feat(m01): 텍스트 수식 조각 글 — 찾은 줄·칸 · 중간값 이름 · 만약 쉬운 말과 흐림 · 돈 정수
- 표 조각 글 = 「찾은 줄 · 칸」(합판 · 1회사용) · 참조.행 = 찾기 조건 - 중간값(재료율 · 인력율)은 글에 이름 · 값은 별도 칸 - 만약 = 「폼타이 가 사용 이면 A · 아니면 B」 · 안 걸린 갈래 흐림 · 조건 입력이 비면 두 갈래 다 - 조건 입력이 비어도 이름 식이 멈추지 않음(값 없이 1,354 중 멈춤 180 → 0) - 글 안의 돈(단가 · 금액)은 정수 · 계산 값은 소수 그대로 검증: test_m01_text 21개 통과 · GF000160 표 조각 일곱 줄 글 모두 다름 · GF000219 금액 앞과 같음. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACe3CR4gBWhWUN5p44fLe4
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
from decimal import ROUND_HALF_UP, Decimal
|
||||
|
||||
import master_copy as mc
|
||||
import master_formula as mf
|
||||
@@ -44,6 +44,13 @@ def fmt(value) -> str:
|
||||
return sign + f"{int(whole):,}" + (f".{rest}" if rest else "")
|
||||
|
||||
|
||||
def fmt_money(value) -> str:
|
||||
"""돈(단가 · 금액)은 원 단위 정수로 보임 — 글만 그렇고 계산 값은 소수 그대로."""
|
||||
if isinstance(value, Decimal):
|
||||
value = value.quantize(Decimal(1), rounding=ROUND_HALF_UP)
|
||||
return fmt(value)
|
||||
|
||||
|
||||
# ── 식을 글로 — 값 꼴과 이름 꼴 ────────────────────────────────────────
|
||||
def render(node, env: dict, master, depth: int = 0) -> str:
|
||||
"""식 한 마디 → 값 꼴 글. 값이 정해지는 마디(요소 · 찾기 · 로직 · 이름)는 그 수로 바뀜."""
|
||||
@@ -82,8 +89,12 @@ def named(show, real, env: dict, master, depth: int = 0) -> str:
|
||||
return _bin(show, real, env, master, depth, named)
|
||||
if kind == "call":
|
||||
if show[1] == "만약": # 값 꼴과 같은 쪽만 적어 두 칸이 짝이 되게
|
||||
at = 1 if mf.evaluate(real[2][0], env, master, depth) else 2
|
||||
return named(show[2][at], real[2][at], env, master, depth)
|
||||
try:
|
||||
at = 1 if mf.evaluate(real[2][0], env, master, depth) else 2
|
||||
except _CALC_ERRORS: # 조건 입력이 비면 식 그대로(아래 부르기 꼴)
|
||||
at = 0
|
||||
if at:
|
||||
return named(show[2][at], real[2][at], env, master, depth)
|
||||
args = ", ".join(named(s, r, env, master, depth) for s, r in zip(show[2], real[2]))
|
||||
return f"{show[1]}({args})"
|
||||
if kind in ("num", "str"):
|
||||
@@ -163,8 +174,22 @@ def _elem_piece(master, ref: str) -> dict:
|
||||
return piece
|
||||
|
||||
|
||||
def _row_label(table: dict, row: dict, conds: dict) -> list[str]:
|
||||
"""찾은 줄의 이름 — 조건마다 그 줄의 값(범위 칸은 찾은 범위)."""
|
||||
out = []
|
||||
for cond, want in conds.items():
|
||||
have = row.get(cond, want)
|
||||
if table.get("조건", {}).get(cond) == "범위" and isinstance(have, list):
|
||||
low, high = ("" if x is None else fmt(x) for x in have)
|
||||
out.append(f"{low}~{high}")
|
||||
else:
|
||||
out.append(fmt(have))
|
||||
return out
|
||||
|
||||
|
||||
def _alias_piece(mark: dict, env: dict, master, column: str | None) -> dict:
|
||||
"""표 찾기·로직 부르기 알약 — 글은 별칭 이름. 참조는 다시 찾아 봐서 되면만 붙임."""
|
||||
"""표 찾기·로직 부르기 알약 — 표는 글 = 「찾은 줄 · 칸」 · 참조.행 = 찾기 조건.
|
||||
다시 못 찾으면(입력이 비어서 따위) 별칭 이름만 남김."""
|
||||
piece = {"kind": "표" if mark["종류"] == "찾기" else "로직", "글": mark["이름"]}
|
||||
try:
|
||||
node = mf.parse(mark["원문"])
|
||||
@@ -173,12 +198,9 @@ def _alias_piece(mark: dict, env: dict, master, column: str | None) -> dict:
|
||||
table = master.get(ref)
|
||||
conds = {k: mf.evaluate(v, env, master, 0) for k, v in node[2]}
|
||||
row = mf.find(table, conds, column)
|
||||
piece["참조"] = {
|
||||
"테이블": ref[:2],
|
||||
"키": ref,
|
||||
"행": row.get("키") or row.get("원문번호"),
|
||||
"열": column,
|
||||
}
|
||||
tail = column.rpartition("_")[2] if column else ""
|
||||
piece["글"] = " · ".join([*_row_label(table, row, conds), *([tail] if tail else [])])
|
||||
piece["참조"] = {"테이블": ref[:2], "키": ref, "행": conds, "열": column}
|
||||
else:
|
||||
piece["참조"] = {"테이블": ref[:2], "키": ref}
|
||||
except _CALC_ERRORS:
|
||||
@@ -186,19 +208,48 @@ def _alias_piece(mark: dict, env: dict, master, column: str | None) -> dict:
|
||||
return piece
|
||||
|
||||
|
||||
def _if_pieces(show, real, env: dict, master, alias_by_name: dict, ifdepth: int) -> list[dict]:
|
||||
"""만약() 조각 — 계산되면 걸린 가지만 · 안 되면 양쪽 다(깊이를 적어 접을 수 있게)."""
|
||||
def _ga(word: str) -> str:
|
||||
"""받침이 없으면 「가」 · 있으면 「이」."""
|
||||
last = word[-1:]
|
||||
return "이" if "가" <= last <= "힣" and (ord(last) - 0xAC00) % 28 else "가"
|
||||
|
||||
|
||||
def _easy(show, real, env: dict, master) -> str:
|
||||
if show[0] == "str":
|
||||
return show[1]
|
||||
try:
|
||||
taken = mf.evaluate(real[2][0], env, master, 0)
|
||||
return named(show, real, env, master)
|
||||
except _CALC_ERRORS:
|
||||
deeper = ifdepth + 1
|
||||
left = _pieces(show[2][1], real[2][1], env, master, alias_by_name, deeper)
|
||||
right = _pieces(show[2][2], real[2][2], env, master, alias_by_name, deeper)
|
||||
for one in (*left, *right):
|
||||
one["깊이"] = deeper
|
||||
return [*left, _p_sym("/"), *right]
|
||||
at = 1 if taken else 2
|
||||
return _pieces(show[2][at], real[2][at], env, master, alias_by_name, ifdepth)
|
||||
return ""
|
||||
|
||||
|
||||
def _cond_text(show, real, env: dict, master) -> str:
|
||||
"""만약 조건 → 쉬운 말 — 「폼타이 가 사용 이면」(같음) · 그 밖은 「식 이면」."""
|
||||
if show[0] == "bin" and show[1] == "==":
|
||||
left = _easy(show[2], real[2], env, master)
|
||||
return f"{left} {_ga(left)} {_easy(show[3], real[3], env, master)} 이면"
|
||||
return f"{_easy(show, real, env, master)} 이면"
|
||||
|
||||
|
||||
def _if_pieces(show, real, env: dict, master, alias_by_name: dict, ifdepth: int) -> list[dict]:
|
||||
"""만약() 조각 — 「조건 이면 A · 아니면 B」 · 안 걸린 갈래는 흐림 ·
|
||||
조건이 안 셈해지면(입력이 비어서 따위) 흐림 없이 두 갈래 다(깊이를 적어 접을 수 있게)."""
|
||||
try:
|
||||
taken = bool(mf.evaluate(real[2][0], env, master, 0))
|
||||
except _CALC_ERRORS:
|
||||
taken = None
|
||||
deeper = ifdepth if taken is not None else ifdepth + 1
|
||||
then = _pieces(show[2][1], real[2][1], env, master, alias_by_name, deeper)
|
||||
other = _pieces(show[2][2], real[2][2], env, master, alias_by_name, deeper)
|
||||
other.insert(0, {"kind": "글", "글": "· 아니면"})
|
||||
for one in then if taken is None else ():
|
||||
one["깊이"] = deeper
|
||||
for one in other if taken is None else ():
|
||||
one["깊이"] = deeper
|
||||
for one in then if taken is False else other if taken else ():
|
||||
one["흐림"] = True
|
||||
cond = {"kind": "글", "글": _cond_text(show[2][0], real[2][0], env, master)}
|
||||
return [cond, *then, *other]
|
||||
|
||||
|
||||
def _side_pieces(
|
||||
@@ -242,10 +293,13 @@ def _pieces(show, real, env: dict, master, alias_by_name: dict, ifdepth: int = 0
|
||||
return [_alias_piece(alias_by_name[show[1][1]], env, master, show[2])]
|
||||
if kind == "name" and show[1] in alias_by_name:
|
||||
return [_alias_piece(alias_by_name[show[1]], env, master, None)]
|
||||
piece = _p_val(show[1] if kind == "name" else "") # 중간 값은 글에 그 이름
|
||||
try:
|
||||
return [_p_val(_value(real, env, master, 0))]
|
||||
value = _value(real, env, master, 0)
|
||||
piece.update({"값": value} if kind == "name" else {"글": value})
|
||||
except _CALC_ERRORS:
|
||||
return [_p_val("")] # 그 밖(값 계산 자체가 막힘) — 빈 값 조각으로 줄이 안 죽게
|
||||
pass # 값 계산 자체가 막힘 — 이름만(그 밖은 빈 글) 남겨 줄이 안 죽게
|
||||
return [piece]
|
||||
|
||||
|
||||
def _formula_pieces(text, env: dict, master, name_of: dict, alias_by_name: dict) -> list[dict]:
|
||||
@@ -303,7 +357,7 @@ def _line_text(item: dict, line: dict, env: dict, master, part: str | None) -> s
|
||||
qty = render(mf.parse(item["수량"]), env, master)
|
||||
money = line["비목"][part] if part else line["금액"]
|
||||
price = line["단가"] if part is None or line["수량"] == 0 else money / line["수량"]
|
||||
return f"{fmt(price)} * {qty} = {fmt(money)}"
|
||||
return f"{fmt_money(price)} * {qty} = {fmt_money(money)}"
|
||||
|
||||
|
||||
def _line_names(item: dict, line: dict, env: dict, master, part: str | None, name_of: dict) -> str:
|
||||
@@ -316,7 +370,7 @@ def _line_names(item: dict, line: dict, env: dict, master, part: str | None, nam
|
||||
|
||||
|
||||
def _extra_text(extra: dict, line: dict, env: dict, master) -> str:
|
||||
return f"{render(mf.parse(extra['식']), env, master)} = {fmt(line['금액'])}"
|
||||
return f"{render(mf.parse(extra['식']), env, master)} = {fmt_money(line['금액'])}"
|
||||
|
||||
|
||||
def _extra_names(extra: dict, env: dict, master, name_of: dict) -> str:
|
||||
@@ -352,11 +406,15 @@ def lines(files: dict, key: str, given: dict) -> dict:
|
||||
if "결과" in row:
|
||||
조각 = _formula_pieces(row["결과"]["식"], env, master, name_of, alias_by_name)
|
||||
got = mc.plain(row.get("이름")) or "결과"
|
||||
shown = (
|
||||
fmt_money if str(row.get("결과단위") or "").startswith("원") else fmt
|
||||
) # 돈 결과만 정수
|
||||
이름글 = f"{_names(row['결과']['식'], env, master, name_of)} = {got}"
|
||||
if result is not None:
|
||||
one = {
|
||||
"이름": row.get("이름") or "",
|
||||
"글": f"{render(mf.parse(row['결과']['식']), env, master)} = {fmt(result['결과'])}",
|
||||
"글": f"{render(mf.parse(row['결과']['식']), env, master)} = "
|
||||
f"{shown(result['결과'])}",
|
||||
"이름글": 이름글,
|
||||
"금액": result["결과"],
|
||||
"조각": 조각,
|
||||
|
||||
Reference in New Issue
Block a user