Files
Aislo/resources/tester/test_802_1_formula_doc.py
T
eomsangdonandClaude Opus 5.5 f7038cc280 feat(802-1): 단가산출 수식 문서 꼴 · 계산기(파이썬 · TS 짝) · 표지 · 꼴 문서
- 꼴: 변수표(기호 · 첨자 · 이름 · 단위 · 연결 = 숫자 · 선택 · 입력 · 요소 · 표 · 단가산출 · 식) + 식 줄 + 금액 줄 + 끝수 칸 — resources/master_data/_수식문서_틀.md
- 풀이 · 글로 되돌림 · 기호 · 이름 바꿈 전파 · 정적 검사 = master_doc.py ↔ common_util_formula_doc.ts
- 셈 = master_doc_calc.py ↔ common_util_formula_doc_calc.ts — 기호는 필요할 때 셈 · 정의 필요 · 순환 · 0 나눔 · 식 틀림 · 빈 문서 = 표지 + 금액 없음 · 값 없음 = 그 줄만 비움 · 묶음 소계 · 합계 · 줄 자리 · 끝전 큰나머지 · 계 끝수 · 조정 · 번지 · 단계 · 합밖
- M01 연결 풀이(표 찾기 · 요소 · 단가산출 문서) = master_doc_link.py ↔ common_util_formula_doc_table.ts(표 찾기) · 옛 엔진 셈 안 부름
- 옛 로직 변환 = master_doc_convert.py(802-2 빌더가 부름)
- 시험 test_802_1_formula_doc 48(꼴 · 셈 · 지움 견딤 300 넘는 지운 문서 예외 0 · 표지 · 거울 파이썬 ↔ TS 60 문서)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:41:39 +09:00

589 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""802-1 단가산출 수식 문서 — 꼴 · 셈 · 지움 견딤 · 표지 · 거울(파이썬 ↔ TS) 지킴 시험.
꼴 = `resources/master_data/_수식문서_틀.md` · 파이썬 = `master_doc*.py` · TS = `common_util_formula_doc*.ts`.
① 식 풀이 · 글로 되돌림 · 기호 · 이름 바꿈 전파.
② 셈 — 입력 · 선택 · 숫자 · 식 · 만약 · % · 표 조건(범위 규칙) · 요소 · 단가산출 부름 · 묶음 소계 · 합계 ·
반올림 · 줄 자리 · 끝전 큰나머지 · 계 끝수 · 조정 · 번지 · 단계 · 합밖 · 결과 문서.
③ 표지 — 정의 필요 · 순환 · 0 나눔 · 식 틀림 · 빈 문서 · 겹침 = 금액 「—」 · 예외 없음.
④ 지움 견딤 — 변수 · 식 줄 · 금액 줄 · 식 글 하나씩 모두 지워도 예외 0.
⑤ 거울 — 같은 문서 · 같은 연결 값으로 파이썬 · TS 답이 같음(실제 M01 문서 표본 + 위 문서들).
"""
import copy
import importlib
import json
import os
import subprocess
from decimal import Decimal as D
import pytest
from M01_MasterData import M01_MasterData_Store as m01 # scripts 길을 둠
md = importlib.import_module("master_doc")
mc = importlib.import_module("master_doc_calc")
ml = importlib.import_module("master_doc_link")
cv = importlib.import_module("master_doc_convert")
HERE = os.path.dirname(os.path.abspath(__file__))
COSTS = ("노무비", "재료비", "경비", "계")
# ── 시험 연결 ─────────────────────────────────────────────────────────
TABLE = {
"키": "QX990001",
"원문번호": "9-9-9",
"조건": {"유형": "고르기", "두께": "범위"},
"범위규칙": "초과·이하",
"값칸": {"품": "인", "율": "%"},
"줄": [
{"유형": "보통", "두께": [None, D(10)], "품": D("0.1"), "율": D(5)},
{"유형": "보통", "두께": [D(10), D(20)], "품": D("0.2"), "율": D(6)},
{"유형": "복잡", "두께": [None, D(20)], "품": D("0.3")},
{"유형": "분수", "품": "1/1.30"},
],
}
class Fake:
"""시험 연결 — 요소 {키: 값 | Gap 까닭} · 표 {키: 표} · 문서 {키: 문서}."""
def __init__(self, elements=None, tables=None, docs=None):
self.elements = elements or {}
self.tables = {"QX990001": TABLE, **(tables or {})}
self.docs = docs or {}
def element(self, p):
key = p.get("키") or json.dumps(p.get("재료"), ensure_ascii=False, sort_keys=True)
if key not in self.elements:
raise mc.Broken(f"없는 요소 「{key}」")
got = self.elements[key]
if isinstance(got, str) and got.startswith("빈:"):
raise mc.Gap(got[2:])
return got[p["칸"]] if p.get("칸") else got
def table(self, p):
if p["표"] not in self.tables:
raise mc.Broken(f"없는 표 「{p['표']}」")
return ml.Links.table(_TableOnly(self.tables[p["표"]]), p)
def doc(self, key):
return self.docs.get(key)
class _TableOnly:
def __init__(self, table):
self.t = table
def _get(self, ref):
return self.t
def _var(sid, link, key=None, name=""):
sign, low, up = md.split_id(sid)
return {
"열쇠": key or sid,
"기호": sign,
"아래": low,
"위": up,
"이름": name or sid,
"연결": link,
}
def _doc(variables=(), lines=(), money=(), **more):
out = md.blank("GX990001", "시험")
out["변수"] = [_var(*v) if isinstance(v, tuple) else v for v in variables]
out["식"] = [{"기호": a, "식": b} for a, b in lines]
out["금액"] = list(money)
out.update(more)
return out
def _man(name, qty, price, kind="노무비", **more):
return {"이름": name, "단위": "인", "비목": kind, "수량": qty, "단가": price, **more}
def _calc(doc, links=None, given=None):
return mc.calc(doc, links or Fake(), given)
def _money(got):
return {k: got[k] for k in COSTS}
def SAMPLE():
"""합판거푸집 꼴 견본 — 선택 · 입력 · 표(범위) · 요소 · 식 · 만약 · % · 덧줄 꼴 금액."""
return _doc(
[
("S_유형", {"갈래": "선택", "목록": ["보통", "복잡"], "값": "보통"}, "유형"),
("t", {"갈래": "입력", "값": D(15), "범위": [0, 100]}, "두께"),
(
"Q^표",
{
"갈래": "표",
"표": "QX990001",
"조건": {"유형": "S_유형", "두께": "t"},
"칸": "품",
},
),
("w_형틀", {"갈래": "요소", "키": "LB990001"}),
("w_보통", {"갈래": "요소", "키": "LB990002"}),
("r_할증", {"갈래": "숫자", "값": D(10)}),
("Q_형틀", {"갈래": "식"}),
],
[("Q_형틀", "Q^표 × (1 + 만약(S_유형 == '복잡', r_할증, 0)%)")],
[
_man("형틀목공", "Q_형틀", "w_형틀", 기호="C_형틀"),
_man("보통인부", "0.05", "w_보통"),
{"이름": "공구손료", "단위": "식", "비목": "경비", "단가": "노무비 × 3%"},
{"이름": "소모", "단위": "식", "비목": "재료비", "단가": "C_형틀 × 1 ÷ 3"},
],
)
LABOR = {"LB990001": D(274978), "LB990002": D(165545)}
# ── ① 꼴 ──────────────────────────────────────────────────────────────
@pytest.mark.parametrize(
"text, want",
[
("a*b/c", "a × b ÷ c"),
("a/(b*c)", "a ÷ (b × c)"),
("a-(b-c)", "a - (b - c)"),
("(a-b)-c", "a - b - c"),
("-x*2", "-x × 2"),
("-(a+b)", "-(a + b)"),
("x %", "x%"),
("(a+b)%", "(a + b)%"),
("3600×q_s×K×f×E_s÷C_m", "3600 × q_s × K × f × E_s ÷ C_m"),
("만약(S=='보통' 그리고 t<=10, 1, 0)", "만약(S == '보통' 그리고 t <= 10, 1, 0)"),
("a ≤ b", "a <= b"),
("(a<b) == (c<d)", "(a < b) == (c < d)"),
("B_굴착기.경비 × Q", "B_굴착기.경비 × Q"),
("번지.A01.재료비 × 11 ÷ 100", "번지.A01.재료비 × 11 ÷ 100"),
("단계.'설치 소계'.계", "단계.'설치 소계'.계"),
("Q_시공^표 − 0.50", "Q_시공^표 - 0.50"),
("C_{m 3} + x_1", "C_{m 3} + x_1"),
("반올림(3600 × q ÷ C, 3)", "반올림(3600 × q ÷ C, 3)"),
("a 또는 b 그리고 c", "a 또는 b 그리고 c"),
("(a 또는 b) 그리고 c", "(a 또는 b) 그리고 c"),
],
)
def test_식_풀이_되돌림(text, want):
got = md.text_of(md.parse(text))
assert got == want
assert md.text_of(md.parse(got)) == got
@pytest.mark.parametrize(
"text", ["", "a +", "(a", "모름(1)", "a b", "'열림", "1 ++", "만약", "x._"]
)
def test_틀린_식(text):
with pytest.raises(md.DocError):
md.parse(text)
def test_기호_글():
assert md.sym_id("Q", "시공", "표") == "Q_시공^표"
assert md.sym_id("C", "m 3") == "C_{m 3}"
assert md.split_id("Q_시공^표") == ("Q", "시공", "표")
assert md.split_id("C_{m 3}") == ("C", "m 3", "")
assert md.good_id("q′_s") and not md.good_id("노무비") and not md.good_id("1a")
def test_이름_바꿈_전파():
doc = SAMPLE()
doc["변수"].append(_var("P_x", {"갈래": "요소", "키": "EQ:{S_유형}", "칸": "가격"}))
md.rename(doc, "S_유형", "U", "형")
assert doc["변수"][0]["기호"] == "U" and doc["변수"][0]["열쇠"] == "유형"
assert doc["변수"][2]["연결"]["조건"]["유형"] == "U_형"
assert doc["식"][0]["식"] == "Q^표 × (1 + 만약(U_형 == '복잡', r_할증, 0)%)"
assert doc["변수"][-1]["연결"]["키"] == "EQ:{U_형}"
md.rename(doc, "C_형틀", "M")
assert doc["금액"][0]["기호"] == "M" and doc["금액"][3]["단가"] == "M × 1 ÷ 3"
with pytest.raises(md.DocError):
md.rename(doc, "M", "t") # 같은 기호
with pytest.raises(md.DocError):
md.rename(doc, "t", "노무비") # 못 쓰는 기호
# ── ② 셈 ──────────────────────────────────────────────────────────────
def test_셈_기본_금액_식():
got = _calc(SAMPLE(), Fake(LABOR))
assert got["셈됨"] and got["표지"] == []
# 형틀 0.2 × 274,978 = 54,995.6 · 보통 0.05 × 165,545 = 8,277.25 → 8,277.2
# 공구손료 = (54,995.6 + 8,277.2) × 3% = 1,898.184 → 1,898.1 · 소모 = 54,995.6 ÷ 3 = 18,331.8
assert [x["금액"] for x in got["줄"]] == [D("54995.6"), D("8277.2"), D("1898.1"), D("18331.8")]
assert _money(got) == {"노무비": 63272, "재료비": 18331, "경비": 1898, "계": 83501}
assert got["끝수"]["줄"] == {"방법": "버림", "자리": 1} and got["머리"] == "계"
got = _calc(SAMPLE(), Fake(LABOR), {"유형": "복잡", "두께": D(5)}) # 넘긴 입력 = 열쇠
assert got["값"]["Q_형틀"] == D("0.33")
def test_표_조건_범위_분수():
links = Fake(LABOR)
doc = SAMPLE()
assert _calc(doc, links, {"두께": D(10)})["값"]["Q^표"] == D("0.1") # 초과·이하 — 10 은 아래 줄
assert _calc(doc, links, {"두께": D("10.5")})["값"]["Q^표"] == D("0.2")
got = _calc(doc, links, {"두께": D(25)}) # 맞는 줄 없음 = 값 없음 → 그 줄만 비움
assert got["셈됨"] and "Q^표" in got["빈값"] and got["줄"][0]["금액"] is None
assert got["노무비"] == 8277 # 형틀 줄 빠짐
doc["변수"].append(
_var("k", {"갈래": "표", "표": "QX990001", "조건": {"유형": "'분수'"}, "칸": "품"})
)
assert _calc(doc, links)["값"]["k"] == D(1) / D("1.30")
doc["변수"].append(
_var("z", {"갈래": "표", "표": "QX990001", "조건": {"유형": "'복잡'"}, "칸": "율"})
)
assert "z" in _calc(doc, links)["빈값"] # 그 줄에 칸 없음 = 값 없음
doc["변수"].append(
_var("y", {"갈래": "표", "표": "QX990001", "조건": {"없는": "1"}, "칸": "품"})
)
assert any(m["갈래"] == "연결 끊김" for m in _calc(doc, links)["표지"]) # 없는 조건 = 끊김
def test_요소_값_없음_줄만_비움():
got = _calc(
SAMPLE(), Fake({"LB990001": D(274978), "LB990002": "빈:값 없음 — 관리자가 채울 값"})
)
assert got["셈됨"] and got["줄"][1]["까닭"].startswith("값 없음")
assert got["노무비"] == 54995
def test_단가산출_부름_묶음_칸_결과():
hour = _doc(
[("w", {"갈래": "숫자", "값": D(100000)}), ("m", {"갈래": "숫자", "값": D("3000.7")})],
[],
[_man("조종원", "1", "w"), {"이름": "연료", "비목": "재료비", "단가": "m"}],
)
work = _doc(
[("q", {"갈래": "입력", "값": D("0.6")}, "버킷"), ("Q", {"갈래": "식"})],
[("Q", "반올림(3600 × q ÷ 18, 3)")],
결과="Q",
)
main = _doc(
[
("B", {"갈래": "단가산출", "키": "GX990002", "입력": {}}),
("B_경", {"갈래": "단가산출", "키": "GX990002", "입력": {}, "칸": "경비"}),
("Q_작업", {"갈래": "단가산출", "키": "GX990003", "입력": {"버킷": "0.7"}}),
],
[],
[
{"이름": "굴착기", "비목": "묶음", "수량": "1 ÷ Q_작업", "단가": "B", "묶음": "굴착기"},
_man("보통인부", "0.01", "B.계", 묶음="인력"),
],
)
links = Fake(docs={"GX990002": hour, "GX990003": work})
assert _calc(work, links)["결과"] == D(120)
got = _calc(main, links)
assert got["값"]["Q_작업"] == D(140) and got["값"]["B"]["계"] == D(103000)
assert got["값"]["B_경"] == 0
assert [x["이름"] for x in got["묶음"]] == ["굴착기", "인력"] and got["머리"] == "합계"
# 굴착기 = 100,000 ÷ 140 = 714.28 → 714.2 · 3,000 ÷ 140 = 21.42 → 21.4 · 인력 0.01 × 103,000 = 1,030
assert got["줄"][0]["비목"] == {"노무비": D("714.2"), "재료비": D("21.4"), "경비": D(0)}
assert got["묶음"][0]["계"] == D("735.6") and _money(got)["계"] == 1765
def test_반올림_자리_끝전_계_조정():
doc = _doc(
[("a", {"갈래": "숫자", "값": D("0.35")})],
[],
[
_man("갑", "1", "반올림(a × 10, 0)", 자리={"방법": "반올림", "자리": 0}),
_man("을", "1", "올림(a, 1) + 버림(a, 1)", "재료비"),
_man("병", "1", "1.6", "경비"),
],
)
got = _calc(doc)
assert [x["금액"] for x in got["줄"]] == [D(4), D("0.7"), D("1.6")]
assert _money(got) == {"노무비": 4, "재료비": 0, "경비": 1, "계": 5}
doc["끝수"]["끝전"] = "큰나머지" # 4 + 0.7 + 1.6 = 6.3 → 6 · 모자란 1 원 = 원 미만 큰 재료비
assert _money(_calc(doc)) == {"노무비": 4, "재료비": 1, "경비": 1, "계": 6}
doc["조정"] = {"배율": {"노무비": D("0.5")}, "옮김": "경비"}
assert _money(_calc(doc)) == {"노무비": 0, "재료비": 0, "경비": 4, "계": 4}
doc.pop("조정")
doc["끝수"] = {
"줄": {"방법": "버림", "자리": 0},
"비목": {"방법": "버림", "자리": 0},
"계": {"방법": "반올림", "자리": -1},
}
assert any(m["갈래"] == "끝수" for m in _calc(doc)["표지"]) # 자리 꼴 틀림 = 표지 · 멈춤
def test_번지_단계_합밖():
doc = _doc(
[],
[],
[
{"이름": "갑", "비목": "재료비", "단가": "1000", "번지": "A01"},
{"이름": "을", "비목": "재료비", "단가": "500", "번지": "A01"},
{"이름": "형틀", "비목": "노무비", "단가": "2000", "번지": "A02"},
{"이름": "소모", "비목": "재료비", "단가": "번지.A01.재료비 × 10 ÷ 100"},
{"이름": "참고", "비목": "경비", "단가": "999", "합밖": True},
{"이름": "손료", "비목": "경비", "단가": "단계.'설치'.계 × 1 ÷ 100"},
],
단계=[
{"이름": "자재", "단": 1, "줄": ["갑", "을", "소모"]},
{"이름": "설치", "단": 1, "줄": ["형틀"]},
{"이름": "계", "단": 2},
],
)
got = _calc(doc)
assert [x["금액"] for x in got["줄"]] == [1000, 500, 2000, 150, 999, 20]
assert _money(got) == {"노무비": 2000, "재료비": 1650, "경비": 20, "계": 3670}
assert [x["계"] for x in got["단계"]] == [1650, 2000, 3650] and got["줄"][4]["합밖"]
def test_결과_문서_입력_빔():
doc = _doc(
[("q", {"갈래": "입력", "값": None}), ("Q", {"갈래": "식"})], [("Q", "q × 2")], 결과="Q"
)
got = _calc(doc)
assert got["결과"] is None and not got["셈됨"] and "비어 있음" in got["까닭"]
assert _calc(doc, given={"q": "3"})["결과"] == 6 # 글 수 = 수
# ── ③ 표지 ────────────────────────────────────────────────────────────
def _kinds(got):
return {m["갈래"] for m in got["표지"]}
@pytest.mark.parametrize(
"lines, kind",
[
([("a", "b + 1")], "정의 필요"),
([("a", "b + 1"), ("b", "a × 2")], "순환"),
([("a", "1 ÷ (2 - 2)")], "0 나눔"),
([("a", "1 +")], "식 틀림"),
([("a", "'글' × 2")], "셈 틀림"),
([("a", "노무비 × 2")], "금액 이름"),
],
)
def test_표지_금액_줄표(lines, kind):
doc = _doc([], lines, [_man("갑", "1", "a")])
got = _calc(doc)
assert kind in _kinds(got)
assert not got["셈됨"] and got["계"] is None and got["노무비"] is None
def test_빈_문서_겹침_순환_부름():
got = _calc(md.blank())
assert not got["셈됨"] and got["계"] is None and _kinds(got) == {"빈 문서"}
doc = _doc([("a", {"갈래": "숫자", "값": D(1)})], [("a", "2")], [_man("갑", "1", "a")])
assert "겹침" in _kinds(_calc(doc)) and _calc(doc)["계"] is None
loop = _doc(
[("B", {"갈래": "단가산출", "키": "GX990001", "입력": {}})], [], [_man("갑", "1", "B.계")]
)
got = _calc(loop, Fake(docs={"GX990001": loop}))
assert not got["셈됨"] and "순환" in _kinds(got)
def test_정적_검사():
doc = SAMPLE()
assert md.check(doc) == []
doc["식"].append({"기호": "x", "식": "y + Q_형틀"})
doc["식"].append({"기호": "y", "식": "x"})
kinds = [m["갈래"] for m in md.check(doc)]
assert "순환" in kinds and "정의 필요" not in kinds
# ── ④ 지움 견딤 ───────────────────────────────────────────────────────
_ROWS: dict = {}
class Lazy(ml.Links):
"""M01 연결 + 부르는 문서는 그때 변환(옛 로직 → 수식 문서)."""
def doc(self, key):
try:
row = self.master.get(key)
except Exception:
return None
key = str(row["키"])
if key not in self.docs and key in _ROWS:
self.docs[key] = cv.convert(self.master, _ROWS[key])[0]
return self.docs.get(key)
def _real_docs(keys):
files, whole = m01.loaded()
_ROWS.update({r["키"]: r for d in files.values() if d.get("그룹") == "로직" for r in d["줄"]})
links = Lazy(whole, {})
return links, {key: links.doc(key) for key in keys}
REAL = (
"GC000211",
"GF000294",
"GC001147",
"GC000100",
"GF000185",
"GC000087",
"GF000221",
"GC000278",
)
def _cuts(doc):
"""지운 문서들 — 변수 · 식 줄 · 금액 줄 하나씩 · 식 글 하나씩 빈 글 · 반 토막."""
for kind in ("변수", "식", "금액"):
for i in range(len(doc.get(kind) or [])):
one = copy.deepcopy(doc)
del one[kind][i]
yield one
for i, line in enumerate(doc.get("식") or []):
for text in ("", line["식"][: len(line["식"]) // 2]):
one = copy.deepcopy(doc)
one["식"][i]["식"] = text
yield one
for i, line in enumerate(doc.get("금액") or []):
for k in ("수량", "단가"):
one = copy.deepcopy(doc)
one["금액"][i][k] = ""
yield one
one = copy.deepcopy(doc)
one.pop("결과", None)
yield one
def test_지움_견딤_예외_0():
links, docs = _real_docs(REAL)
count = 0
for doc in [SAMPLE(), *docs.values()]:
base = _calc(doc, links if doc.get("키") in docs else Fake(LABOR))
assert base["셈됨"]
for one in _cuts(doc):
got = _calc(one, links if doc.get("키") in docs else Fake(LABOR)) # 예외 없음
count += 1
assert set(got) >= {"값", "표지", "셈됨", "끝수"}
if not got["셈됨"]:
assert got.get("계") is None and got.get("결과") is None
assert got["표지"] or got.get("까닭")
assert count > 300
# ── ⑤ 거울 ────────────────────────────────────────────────────────────
def _canon(v):
if isinstance(v, (D, int, float)) and not isinstance(v, bool):
return mc.plain_text(D(str(v)))
if isinstance(v, list):
return [_canon(x) for x in v]
if isinstance(v, dict):
return {k: _canon(v[k]) for k in sorted(v)}
return v
class Rec:
"""연결 기록 — 같은 연결 값을 TS 에 넘김."""
def __init__(self, links):
self.links, self.log = links, {}
def _keep(self, kind, p, fn):
key = f"{kind}|{json.dumps(_canon(p), ensure_ascii=False, separators=(',', ':'))}"
try:
got = fn(p)
except mc.Gap as e:
self.log[key] = {"빈": str(e)}
raise
except mc.Broken as e:
self.log[key] = {"끊김": str(e)}
raise
self.log[key] = {"값": got}
return got
def element(self, p):
return self._keep("요소", p, self.links.element)
def table(self, p):
return self._keep("표", p, self.links.table)
def doc(self, key):
return self.links.doc(key)
def _ts(payload):
proc = subprocess.run(
["node", os.path.join(HERE, "helper_802_1_formula_doc.cjs")],
input=json.dumps(ml._norm(payload), ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
check=True,
)
return json.loads(proc.stdout)
def _same(a, b, where=""):
if isinstance(a, dict):
assert isinstance(b, dict) and set(a) == set(b), (where, sorted(a), sorted(b or {}))
for k in a:
_same(a[k], b[k], f"{where}.{k}")
elif isinstance(a, list):
assert isinstance(b, list) and len(a) == len(b), where
for i, (x, y) in enumerate(zip(a, b)):
_same(x, y, f"{where}[{i}]")
elif isinstance(a, (D, int, float)) and not isinstance(a, bool):
assert isinstance(b, (int, float)) and not isinstance(b, bool), (where, a, b)
assert float(a) == pytest.approx(b, rel=1e-9, abs=1e-9), (where, a, b)
else:
assert a == b, (where, a, b)
def test_거울_파이썬_TS_같은_답():
links, _ = _real_docs(())
picks = sorted(_ROWS)[::30] + list(REAL)
for key in picks:
links.doc(key)
rec = Rec(links)
want = {key: mc.calc(links.docs[key], rec) for key in picks}
all_docs = links.docs
fakes = {
"SAMPLE": (SAMPLE(), Fake(LABOR)),
"빈": (md.blank(), Fake()),
"순환": (_doc([], [("a", "b"), ("b", "a")], [_man("갑", "1", "a")]), Fake()),
"0나눔": (_doc([], [("a", "1 ÷ 0")], [_man("갑", "1", "a")]), Fake()),
}
for name, (doc, links) in fakes.items():
one = Rec(links)
want[name] = mc.calc(doc, one)
rec.log.update(one.log)
all_docs[name] = doc
texts = [
"a*b/c",
"-(a+b)%",
"만약(S=='x', 1, 2)",
"단계.'소계'.계 × 2",
"a +",
"C_{m 3}^표 ÷ 2",
]
cases = [
{"조건": {"유형": "보통", "두께": D(t)}, "칸": "품"}
for t in ("5", "10", "10.5", "20", "25")
] + [{"조건": {"유형": "분수"}, "칸": "품"}, {"조건": {"유형": "복잡"}, "칸": "율"}]
got = _ts(
{
"문서들": all_docs,
"대상": list(want),
"기록": rec.log,
"글": texts,
"표": [{"표": TABLE, **c} for c in cases],
}
)
for key, answer in want.items():
_same(ml._norm(answer), got["답"][key], key)
for text, ts_text in zip(texts, got["글"]):
try:
assert md.text_of(md.parse(text)) == ts_text
except md.DocError:
assert ts_text == {"틀림": True}
for case, ts_value in zip(cases, got["표"]):
try:
py = {"값": ml.Links.table(_TableOnly(TABLE), {"표": "QX990001", **case})}
except mc.Gap:
py = {"빈": True}
_same(ml._norm(py), ts_value, str(case))
assert len(want) >= 60