feat(812-3): 가져온 단가산출 좌측 차림 — 경쟁사 머리 줄 = 항목 · 변수 대입 = 공식 묶음 · 근거 글 = 글 줄
- 새 M02_Doc_NativeLayout — 원본 글줄 차림 표지로 금액 줄 묶음(= 항목 제목) + 문서 쌓기(기본과 다를 때만) · EST 금액 차례 = 원본 차례(셈 같을 때만) · 합계 절 뺌 · 줄 쌓기 모델 stackOf · saveOf · hiddenOf 파이썬 거울 - M02_Doc_NativeEst.tags — 글줄 차림 머리(단 > 0 · 소계 구간 첫 글 · 번호 글) · 합계 · 글(분수 그림 위아래 줄 뺌) - M02_San_Emit.stmate — 글 줄 표지 · 원본 차림 표지(계 줄마다 항목) - M02_Doc_Import_Shape.shape — 차림 tag · apply 부름 - 줄 쌓기 hiddenOf — 가져온 값 옵션 칸도 공식 묶음에 보임(원파일 꼬리표 · 같은 값) · 옵션 가로 칸 고침 = 수 그대로(글이면 셈 「—」 흠 고침) - 시험 test_812_3 — 견본 다섯(거창 1 · 2 · 41 · 15 · 봉화 14) · 전수 2,220 블록 금액 같음 · TS 왕복 · 거울 100 % · M01 1,518 차림 그대로 - 낡은 시험 고침 test_810_3 — 옵션 칸 ∩ 공식 묶음 = 빈 → 옵션 칸 ⊆ 공식 묶음(812-3 으로 가져온 값이 공식 묶음에도 보임) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -161,7 +161,10 @@ def machines(doc: dict, title: dict[str, str]) -> None:
|
||||
|
||||
def shape(doc: dict, meta: dict) -> dict:
|
||||
"""derive 문서 다듬기 — 원본 글줄 · 엔진 줄 자원 이름."""
|
||||
from M02_MasterTemplete import M02_Doc_NativeLayout as lay
|
||||
|
||||
src = meta.get("원본") or {}
|
||||
lay.tag(doc, meta) # 812-3 원파일 차림 — 금액 줄 ↔ 로직 줄(끝에 `apply`)
|
||||
names(doc, list(src.get("글줄") or []))
|
||||
title = {}
|
||||
for item in (meta.get("로직") or {}).get("호표") or []:
|
||||
@@ -169,4 +172,5 @@ def shape(doc: dict, meta: dict) -> dict:
|
||||
if res.get("갈래") == "기계" and res.get("이름"):
|
||||
title[str(item.get("이름"))] = " ".join(x for x in (res["이름"], res.get("규격")) if x)
|
||||
machines(doc, title)
|
||||
lay.apply(doc, meta)
|
||||
return doc
|
||||
|
||||
@@ -19,6 +19,7 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from M02_MasterTemplete import M02_Doc_NativeLayout as lay
|
||||
from M02_MasterTemplete import M02_San_Emit as em
|
||||
from M02_MasterTemplete import M02_San_Est as est
|
||||
from M02_MasterTemplete import M02_San_Expr as ex
|
||||
@@ -76,6 +77,8 @@ class Block:
|
||||
self.groups: dict[str, list[tuple[str, float]]] = {} # 구간 글자 → (부름 글, 결과)
|
||||
self.extra: dict[int, dict] = {} # 행 → 소계가 정한 비목(아래 줄)
|
||||
self.got = {"확정": 0, "표지": 0, "추정": 0}
|
||||
self.memo: list[int] = [] # 글 줄 행(812-3 차림 · 분수 그림 위아래 줄 뺌)
|
||||
self.frac: set[int] = set()
|
||||
|
||||
# ── 작은 도구 ──────────────────────────────────────────────────
|
||||
def mark(self, ln: dict, why: str) -> None:
|
||||
@@ -182,6 +185,7 @@ class Block:
|
||||
return re.sub(r"\s*([×÷·+*/−-])\s*", r"\1", t).strip().split()
|
||||
|
||||
tops, bottoms, at = cols(i - 1), cols(i + 1), [0]
|
||||
self.frac |= {self.lines[k]["행"] for k in (i - 1, i + 1) if 0 <= k < len(self.lines)}
|
||||
|
||||
def one(_: re.Match) -> str:
|
||||
k = at[0]
|
||||
@@ -485,6 +489,7 @@ class Block:
|
||||
|
||||
def text_line(self, ln: dict) -> None:
|
||||
"""글 줄 — 품셈 · 조건 이름만 있는 글(값 없음) = 「추정」 표지(연결 안 함 · 810-3 화면)."""
|
||||
self.memo.append(ln["행"])
|
||||
if _COND.search(ln["보임"]):
|
||||
self.o.glyph(
|
||||
ln["행"], ln["보임"], mark=f"{NOTE}{GUESS} — 품셈 조건 글(값 없음 · 연결 안 함)"
|
||||
@@ -493,6 +498,22 @@ class Block:
|
||||
return
|
||||
self.o.glyph(ln["행"], ln["보임"])
|
||||
|
||||
def tags(self) -> None:
|
||||
"""812-3 차림 표지(`M02_Doc_NativeLayout`) — 글줄 `차림` = 머리(단 > 0 ·
|
||||
소계 구간 첫 글 · 번호 글) · 합계 · 글(근거 · 메모)."""
|
||||
self.o.head[lay.TAG] = True
|
||||
for ln in self.lines:
|
||||
one = self.o.at.get(ln["행"])
|
||||
if one is None or ln["행"] in self.frac or "자리" in one:
|
||||
continue
|
||||
head = (ln.get("단") or 0) > 0 or ln.get("색인") == -20
|
||||
if ln.get("합계"):
|
||||
one[lay.TAG] = "합계"
|
||||
elif head or (ln["행"] in self.memo and lay.NUMBERED.match(one["글"])):
|
||||
one[lay.TAG] = "머리"
|
||||
elif ln["행"] in self.memo:
|
||||
one[lay.TAG] = "글"
|
||||
|
||||
# ── 한 벌 ─────────────────────────────────────────────────────
|
||||
def run(self) -> dict:
|
||||
for k, ln in enumerate(self.lines): # 「노무비 : ‹단가›」(식 · 결과 없음) = 단가 × 1
|
||||
@@ -544,6 +565,7 @@ class Block:
|
||||
elif (ln.get("보임") or "").strip():
|
||||
self.text_line(ln)
|
||||
i += 1
|
||||
self.tags()
|
||||
saved = self.blk.get("저장") or {}
|
||||
check = {COMP[c]: saved.get(c) or 0 for c in COMP} | {"계": saved.get("T") or 0}
|
||||
out = self.o.done("비목버림", None, check, "산근", self.file)
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
"""가져온 단가산출 줄 쌓기 차림(PLAN 812-3 · 요구 713 · 669~677) — 경쟁사 산출근거 꼴.
|
||||
|
||||
금액 · 셈 그대로. `M02_Doc_Import_Shape.shape` 가 부름(`tag` = 다듬기 앞 · `apply` = 끝) ·
|
||||
원본 `차림` 표지 있는 블록만(EST = `M02_Doc_NativeEst` · STmate = `M02_San_Emit.stmate`):
|
||||
항목 = 머리 줄(글줄 `차림` 「머리」 · 「1. …」 「가. …」 글) · 번호 글은 떼고 화면이 매김 ·
|
||||
가 · 나 = `가나다` · STmate 는 계 줄(`단계:`)마다 끊고 그 뒤 첫 글 줄 = 제목 ·
|
||||
머리 앞 줄 = 제목 없는 항목 · 합계 줄(「합계」 · 「소계」 절)만 있는 머리 = 뺌
|
||||
(화면 소계 · 합계가 냄)
|
||||
금액 줄 = 그 자리(원본 행 차례) — EST 는 셈이 같을 때만 금액 차례를 원본 차례로
|
||||
(단계 · 누계 이름 없고 기호 부름이 앞 줄일 때)
|
||||
공식 묶음 = 변수 대입 줄(`mid:` · 쓰는 차례) · 글 줄 = 근거 · 메모(글줄 `차림` 「글」)
|
||||
저장 = 금액 줄 `묶음` = 항목 제목 + 문서 `쌓기`(기본 차림과 다를 때만).
|
||||
파이썬 · TS 짝 — `common_util/common_util_line_stack.ts` `stackOf` · `saveOf` ·
|
||||
`hiddenOf`(안 놓인 기호 자리 · 기본 차림)의 거울 · 거울 시험
|
||||
`resources/tester/test_812_3_native_layout.py`(TS 왕복). 고칠 때 둘을 같이 고침.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
_SCRIPTS = Path(__file__).resolve().parents[1] / "resources" / "master_data" / "scripts"
|
||||
if str(_SCRIPTS) not in sys.path:
|
||||
sys.path.insert(0, str(_SCRIPTS))
|
||||
|
||||
import master_doc as md # noqa: E402
|
||||
|
||||
from M02_MasterTemplete import M02_Sangeun_Doc as sd # noqa: E402
|
||||
|
||||
TAG = "차림" # 글줄 칸 — 머리 | 합계 | 글 · 원본 칸 = 차림 낼 블록
|
||||
KEEP = "_로직줄" # 문서 금액 줄 ↔ 로직 줄 차례(다듬기 사이 임시)
|
||||
GANADA = "가나다라마바사아자차카타파하"
|
||||
NUMBERED = re.compile(rf"^\s*'?\s*(\d{{1,2}}|[{GANADA}])\s*[.)]\s*(?!\d)(\S.*)$")
|
||||
_TOTAL = {"합계", "소계", "계"}
|
||||
|
||||
|
||||
def tag(doc: dict, meta: dict) -> None:
|
||||
"""다듬기(이름 · 기계 합침) 앞 — 금액 줄마다 로직 줄 차례(호표 → 덧줄)를 적어 둠."""
|
||||
row = meta.get("로직") or {}
|
||||
n = len(row.get("호표") or []) + len(row.get("덧줄") or [])
|
||||
money = doc.get("금액") or []
|
||||
if (meta.get("원본") or {}).get(TAG) and money and len(money) == n:
|
||||
for k, line in enumerate(money):
|
||||
line[KEEP] = k
|
||||
|
||||
|
||||
def title_of(text: str) -> tuple[str, bool]:
|
||||
"""「 1. 기 계(100%)」 → (「기 계(100%)」, 거짓) · 「'가. 굴삭기'」 → (「굴삭기」, 가나다)."""
|
||||
flat = " ".join(str(text or "").replace("'", " ").split())
|
||||
m = NUMBERED.match(flat)
|
||||
if not m:
|
||||
return flat, False
|
||||
return m.group(2).strip(), m.group(1) in GANADA
|
||||
|
||||
|
||||
# ── 차림 ──────────────────────────────────────────────────────────────
|
||||
def apply(doc: dict, meta: dict) -> None:
|
||||
money = doc.get("금액") or []
|
||||
keys = [line.pop(KEEP, None) for line in money]
|
||||
src = meta.get("원본") or {}
|
||||
if not src.get(TAG) or not money or None in keys:
|
||||
return
|
||||
from M02_MasterTemplete import M02_Sangeun_Imported as imp
|
||||
|
||||
row = meta.get("로직") or {}
|
||||
ids = sd.line_ids(
|
||||
imp.row_of({imp.KEY: {"로직": row}}) or {}
|
||||
) # 글줄 자리 = 줄 정체(`imp._named`)
|
||||
lines = [*(row.get("호표") or []), *(row.get("덧줄") or [])]
|
||||
by: dict[str, int] = {}
|
||||
for k, (cid, one) in enumerate(zip(ids, lines)):
|
||||
by.setdefault(cid, k)
|
||||
by.setdefault(str(one.get("이름")), k)
|
||||
inv = {k: j for j, k in enumerate(keys)}
|
||||
texts = sorted(
|
||||
(t for t in src.get("글줄") or [] if isinstance(t.get("행"), int)), key=lambda t: t["행"]
|
||||
)
|
||||
at: dict[int, int] = {} # 문서 금액 줄 → 원본 행
|
||||
for t in texts:
|
||||
for spot in t.get("자리들") or ([t["자리"]] if t.get("자리") else []):
|
||||
j = inv.get(by.get(str(spot), -1))
|
||||
if j is not None:
|
||||
at[j] = min(at.get(j, t["행"]), t["행"])
|
||||
rows = _fill([at.get(j) for j in range(len(money))])
|
||||
order = sorted(range(len(money)), key=lambda j: (rows[j], j))
|
||||
if order != list(range(len(money))) and _safe(doc, order):
|
||||
doc["금액"] = money = [money[j] for j in order]
|
||||
rows = [rows[j] for j in order]
|
||||
else:
|
||||
for j in range(1, len(rows)): # 차례 그대로 — 앞 줄보다 위면 앞 줄 자리로
|
||||
rows[j] = max(rows[j], rows[j - 1])
|
||||
items = _items(doc, texts, rows, src.get("갈래") == "STmate")
|
||||
for it in items:
|
||||
for kind, j in it["줄"]:
|
||||
if kind == "금액":
|
||||
money[j].pop("묶음", None)
|
||||
money[j] |= {"묶음": it["제목"]} if it["제목"] else {}
|
||||
raw = [_save_item(it, money) for it in items]
|
||||
doc.pop("쌓기", None)
|
||||
plain = save_of(stack_of(doc), money)
|
||||
got = save_of(stack_of({**doc, "쌓기": raw}), money)
|
||||
if got != plain:
|
||||
doc["쌓기"] = got
|
||||
|
||||
|
||||
def _fill(rows: list[int | None]) -> list[int]:
|
||||
"""원본 행 없는 줄(글줄에 안 붙은 소계 덧줄 등) = 다음 줄 행 · 없으면 앞 줄 행."""
|
||||
out = list(rows)
|
||||
for j in range(len(out) - 1, -1, -1):
|
||||
if out[j] is None and j + 1 < len(out):
|
||||
out[j] = out[j + 1]
|
||||
for j in range(len(out)):
|
||||
if out[j] is None:
|
||||
out[j] = out[j - 1] if j else 0
|
||||
return [int(x or 0) for x in out]
|
||||
|
||||
|
||||
def _safe(doc: dict, order: list[int]) -> bool:
|
||||
"""금액 차례를 바꿔도 셈이 같음 — 단계 없음 · 누계 · 번지 · 단계 이름 안 씀 ·
|
||||
줄 기호 부름 = 앞 줄."""
|
||||
if doc.get("단계"):
|
||||
return False
|
||||
money = doc.get("금액") or []
|
||||
pos = {j: p for p, j in enumerate(order)}
|
||||
sym = {str(x["기호"]): pos[j] for j, x in enumerate(money) if x.get("기호")}
|
||||
for j, line in enumerate(money):
|
||||
for text in (line.get("수량"), line.get("단가")):
|
||||
if text is None or str(text).strip() == "":
|
||||
continue
|
||||
try:
|
||||
got = md.names(md.parse(text))
|
||||
except md.DocError:
|
||||
return False
|
||||
if any(n in md.MONEY_NAMES or n == "계" for n in got):
|
||||
return False
|
||||
if any(n in sym and sym[n] >= pos[j] for n in got):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _items(doc: dict, texts: list[dict], rows: list[int], stm: bool) -> list[dict]:
|
||||
"""원본 행 차례로 항목 · 줄을 쌓음 — 줄 = (금액, 문서 줄) | (공식, [기호]) | (글, 글)."""
|
||||
have = set(symbols(doc))
|
||||
hidden = hidden_of(doc)
|
||||
ev: list[tuple[int, int, int, str, Any]] = []
|
||||
for t in texts:
|
||||
mark, text = t.get(TAG), str(t.get("글") or "")
|
||||
spots = t.get("자리들") or ([t["자리"]] if t.get("자리") else [])
|
||||
mids = [s[4:] for s in spots if str(s).startswith("mid:")]
|
||||
if any(str(s).startswith("단계:") for s in spots):
|
||||
ev.append((t["행"], 0, 0, "끝", None))
|
||||
elif mark == "합계":
|
||||
ev.append((t["행"], 0, 0, "합계", None))
|
||||
elif not spots and (mark == "머리" or (mark == "글" and NUMBERED.match(text))):
|
||||
ev.append((t["행"], 0, 0, "머리", title_of(text)))
|
||||
elif not spots and mark == "글" and text.strip():
|
||||
ev.append((t["행"], 1, 0, "글", " ".join(text.replace("'", " ").split())))
|
||||
ids = [m for m in mids if m in have and m not in hidden]
|
||||
if ids:
|
||||
ev.append((t["행"], 1, 0, "공식", ids))
|
||||
ev += [(r, 2, j, "금액", j) for j, r in enumerate(rows)]
|
||||
ev.sort(key=lambda e: e[:3])
|
||||
items: list[dict] = []
|
||||
cur: dict | None = None
|
||||
cut = False # STmate 계 줄 뒤 — 다음 첫 글 줄 = 제목
|
||||
|
||||
def new(title: str = "", sub: bool = False) -> dict:
|
||||
it = {"제목": title, "가나다": sub, "줄": [], "합계": False}
|
||||
items.append(it)
|
||||
return it
|
||||
|
||||
for _, _, _, kind, val in ev:
|
||||
if kind == "끝":
|
||||
cur, cut = None, stm
|
||||
elif kind == "합계":
|
||||
if cur is not None:
|
||||
cur["합계"] = True
|
||||
elif kind == "머리" or (kind == "글" and cut and cur is None):
|
||||
title, sub = val if kind == "머리" else title_of(val)
|
||||
if cur is not None and not cur["줄"] and not cur["제목"]:
|
||||
cur["제목"], cur["가나다"] = title, sub
|
||||
else:
|
||||
cur = new(title, sub)
|
||||
cut = False
|
||||
else:
|
||||
cur = cur if cur is not None else new()
|
||||
if kind == "공식" and cur["줄"] and cur["줄"][-1][0] == "공식":
|
||||
cur["줄"][-1][1].extend(x for x in val if x not in cur["줄"][-1][1])
|
||||
else:
|
||||
cur["줄"].append((kind, list(val) if kind == "공식" else val))
|
||||
keep = []
|
||||
for it in items:
|
||||
flat = re.sub(r"\s+", "", it["제목"])
|
||||
if not it["줄"] and (it["합계"] or not flat or flat in _TOTAL):
|
||||
continue
|
||||
keep.append(it)
|
||||
return keep
|
||||
|
||||
|
||||
def _save_item(it: dict, money: list[dict]) -> dict:
|
||||
out: dict[str, Any] = {}
|
||||
if it["제목"]:
|
||||
out["제목"] = it["제목"]
|
||||
if it["가나다"]:
|
||||
out["가나다"] = True
|
||||
lines = []
|
||||
for kind, val in it["줄"]:
|
||||
if kind == "금액":
|
||||
lines.append({"금액": val, "이름": money[val].get("이름") or ""})
|
||||
elif kind == "공식":
|
||||
lines.append({"공식": val})
|
||||
else:
|
||||
lines.append({"글": val})
|
||||
return out | {"줄": lines}
|
||||
|
||||
|
||||
# ── 줄 쌓기 모델 거울(`common_util_line_stack.ts`) ──────────────────────
|
||||
def _names_in(text: Any) -> list[str]:
|
||||
if text is None or str(text).strip() == "":
|
||||
return []
|
||||
try:
|
||||
return md.names(md.parse(text))
|
||||
except md.DocError:
|
||||
return []
|
||||
|
||||
|
||||
def symbols(doc: dict) -> list[str]:
|
||||
out = [md.var_id(v) for v in doc.get("변수") or []]
|
||||
for line in doc.get("식") or []:
|
||||
sid = str(line.get("기호") or "")
|
||||
if sid and sid not in out:
|
||||
out.append(sid)
|
||||
return out
|
||||
|
||||
|
||||
def _is_option(v: dict | None) -> bool:
|
||||
return bool(v and v.get("열쇠") and (v.get("연결") or {}).get("갈래") in ("선택", "입력"))
|
||||
|
||||
|
||||
def uses_of(doc: dict) -> dict[str, list[str]]:
|
||||
out: dict[str, list[str]] = {}
|
||||
|
||||
def add(sid: str, more: list[str]) -> None:
|
||||
now = out.setdefault(sid, [])
|
||||
now += [n for n in more if n not in now]
|
||||
|
||||
for v in doc.get("변수") or []:
|
||||
add(md.var_id(v), md.link_names(v["연결"]) if v.get("연결") else [])
|
||||
for line in doc.get("식") or []:
|
||||
add(str(line.get("기호") or ""), _names_in(line.get("식")))
|
||||
return out
|
||||
|
||||
|
||||
def price_sym(line: dict) -> str | None:
|
||||
text = line.get("단가")
|
||||
if text is None or str(text).strip() == "":
|
||||
return None
|
||||
try:
|
||||
n = md.parse(text)
|
||||
except md.DocError:
|
||||
return None
|
||||
if n[0] == "var":
|
||||
return n[1]
|
||||
if n[0] == "field" and n[1][0] == "var":
|
||||
return n[1][1]
|
||||
return None
|
||||
|
||||
|
||||
def owned_of(doc: dict) -> dict[int, str]:
|
||||
count: dict[str, int] = {}
|
||||
for ns in (
|
||||
*(_names_in(x.get("수량")) + _names_in(x.get("단가")) for x in doc.get("금액") or []),
|
||||
*uses_of(doc).values(),
|
||||
*([[doc["결과"]]] if doc.get("결과") else []),
|
||||
):
|
||||
for n in ns:
|
||||
count[n] = count.get(n, 0) + 1
|
||||
vars_ = {md.var_id(v): v for v in doc.get("변수") or []}
|
||||
lines = {str(x.get("기호") or "") for x in doc.get("식") or []}
|
||||
out = {}
|
||||
for i, line in enumerate(doc.get("금액") or []):
|
||||
s = price_sym(line)
|
||||
kind = ((vars_.get(s) or {}).get("연결") or {}).get("갈래") if s else None
|
||||
if s and kind in ("요소", "단가산출") and count.get(s) == 1 and s not in lines:
|
||||
out[i] = s
|
||||
return out
|
||||
|
||||
|
||||
def hidden_of(doc: dict) -> set[str]:
|
||||
"""차림에 안 넣는 기호 — 옵션 · 금액 줄이 가진 연결(가져온 값 옵션 칸은 공식 묶음에도 보임)."""
|
||||
out = set(owned_of(doc).values())
|
||||
out |= {md.var_id(v) for v in doc.get("변수") or [] if _is_option(v)}
|
||||
return out
|
||||
|
||||
|
||||
def _post_order(doc: dict, roots: list[str], uses: dict[str, list[str]]) -> list[str]:
|
||||
out: list[str] = []
|
||||
seen: set[str] = set()
|
||||
stack: list[tuple[str, int]] = []
|
||||
for start in [*roots, *symbols(doc)]: # 되부름 없이(깊은 식 사슬)
|
||||
if start in seen:
|
||||
continue
|
||||
seen.add(start)
|
||||
stack.append((start, 0))
|
||||
while stack:
|
||||
sid, k = stack.pop()
|
||||
kids = uses.get(sid) or []
|
||||
if k < len(kids):
|
||||
stack.append((sid, k + 1))
|
||||
if kids[k] not in seen:
|
||||
seen.add(kids[k])
|
||||
stack.append((kids[k], 0))
|
||||
else:
|
||||
out.append(sid)
|
||||
return out
|
||||
|
||||
|
||||
def _reach(line: dict, uses: dict[str, list[str]]) -> set[str]:
|
||||
out: set[str] = set()
|
||||
todo = _names_in(line.get("수량")) + _names_in(line.get("단가"))
|
||||
while todo:
|
||||
sid = todo.pop()
|
||||
if sid in out:
|
||||
continue
|
||||
out.add(sid)
|
||||
todo += uses.get(sid) or []
|
||||
return out
|
||||
|
||||
|
||||
def stack_of(doc: dict) -> list[dict]:
|
||||
"""`stackOf` — 저장 차림 + 안 놓인 것은 기본 자리 ·
|
||||
줄 = {꼴 금액 i | 꼴 공식 기호 틀? | 꼴 글 글}."""
|
||||
money = doc.get("금액") or []
|
||||
all_ = set(symbols(doc))
|
||||
hidden = hidden_of(doc)
|
||||
items: list[dict] = []
|
||||
where: dict[int, dict] = {}
|
||||
placed: set[str] = set()
|
||||
saved = doc.get("쌓기") if isinstance(doc.get("쌓기"), list) else []
|
||||
taken: set[int] = set()
|
||||
for s in saved:
|
||||
s = s if isinstance(s, dict) else {}
|
||||
it: dict[str, Any] = {
|
||||
"제목": str(s.get("제목") if s.get("제목") is not None else ""),
|
||||
"줄": [],
|
||||
}
|
||||
if s.get("가나다"):
|
||||
it["가나다"] = True
|
||||
for line in s.get("줄") if isinstance(s.get("줄"), list) else []:
|
||||
if isinstance(line, dict) and "금액" in line:
|
||||
i = int(line["금액"])
|
||||
name = line.get("이름")
|
||||
if not (
|
||||
0 <= i < len(money) and (money[i].get("이름") or "") == name and i not in taken
|
||||
):
|
||||
free = (k for k, m in enumerate(money) if k not in taken)
|
||||
i = next((k for k in free if (money[k].get("이름") or "") == name), -1)
|
||||
if i < 0 or i in taken:
|
||||
continue
|
||||
taken.add(i)
|
||||
where[i] = it
|
||||
it["줄"].append({"꼴": "금액", "i": i})
|
||||
elif isinstance(line, dict) and "공식" in line:
|
||||
got = [
|
||||
x
|
||||
for x in line["공식"] or []
|
||||
if x in all_ and x not in hidden and x not in placed
|
||||
]
|
||||
placed |= set(got)
|
||||
one: dict[str, Any] = {"꼴": "공식", "기호": got}
|
||||
if line.get("틀"):
|
||||
one["틀"] = str(line["틀"])
|
||||
if line.get("틀꼴"):
|
||||
one["틀꼴"] = str(line["틀꼴"])
|
||||
it["줄"].append(one)
|
||||
elif isinstance(line, dict) and "글" in line:
|
||||
it["줄"].append(
|
||||
{"꼴": "글", "글": str(line["글"] if line["글"] is not None else "")}
|
||||
)
|
||||
items.append(it)
|
||||
for i, m in enumerate(money):
|
||||
if i in taken:
|
||||
continue
|
||||
title = str(m.get("묶음") if m.get("묶음") is not None else "")
|
||||
prev = where.get(i - 1)
|
||||
if prev is not None and prev["제목"] == title:
|
||||
it = prev
|
||||
at = next(k for k, x in enumerate(prev["줄"]) if x["꼴"] == "금액" and x["i"] == i - 1)
|
||||
prev["줄"].insert(at + 1, {"꼴": "금액", "i": i})
|
||||
else:
|
||||
it = next((x for x in items if x["제목"] == title), None) if saved else None
|
||||
if it is None:
|
||||
it = {"제목": title, "줄": []}
|
||||
items.append(it)
|
||||
it["줄"].append({"꼴": "금액", "i": i})
|
||||
taken.add(i)
|
||||
where[i] = it
|
||||
uses = uses_of(doc)
|
||||
reach = [_reach(m, uses) for m in money]
|
||||
for sid in _post_order(doc, [doc["결과"]] if doc.get("결과") else [], uses):
|
||||
if sid not in all_ or sid in hidden or sid in placed:
|
||||
continue
|
||||
i = next((k for k, r in enumerate(reach) if sid in r), -1)
|
||||
it = where.get(i) if i >= 0 else (items[0] if items else None)
|
||||
if it is None:
|
||||
it = {"제목": "", "줄": []}
|
||||
items.append(it)
|
||||
block = next((x for x in it["줄"] if x["꼴"] == "공식"), None)
|
||||
if block is None:
|
||||
block = {"꼴": "공식", "기호": []}
|
||||
it["줄"].insert(0, block)
|
||||
block["기호"].append(sid)
|
||||
placed.add(sid)
|
||||
return items
|
||||
|
||||
|
||||
def save_of(items: list[dict], money: list[dict]) -> list[dict]:
|
||||
"""`saveOf`(차례 그대로)."""
|
||||
out = []
|
||||
for it in items:
|
||||
one: dict[str, Any] = {}
|
||||
if it["제목"]:
|
||||
one["제목"] = it["제목"]
|
||||
if it.get("가나다"):
|
||||
one["가나다"] = True
|
||||
lines = []
|
||||
for x in it["줄"]:
|
||||
if x["꼴"] == "금액":
|
||||
lines.append({"금액": x["i"], "이름": money[x["i"]].get("이름") or ""})
|
||||
elif x["꼴"] == "글":
|
||||
lines.append({"글": x["글"]})
|
||||
else:
|
||||
lines.append(
|
||||
{"공식": list(x["기호"])}
|
||||
| ({"틀": x["틀"]} if x.get("틀") else {})
|
||||
| ({"틀꼴": x["틀꼴"]} if x.get("틀꼴") else {})
|
||||
)
|
||||
out.append(one | {"줄": lines})
|
||||
return out
|
||||
@@ -486,11 +486,15 @@ def stmate(
|
||||
me = prices.get(block["코드"]) or {}
|
||||
meta = me.get("meta") or {}
|
||||
o = Out(block.get("이름"), me.get("단위"), "STmate", block["코드"])
|
||||
o.head["차림"] = True # 812-3 — 계 단으로 항목 · 글 줄(`M02_Doc_NativeLayout`)
|
||||
st = _Stm(o, block, prices, keys)
|
||||
for node in nodes:
|
||||
kind, src = node["종류"], {"행": node.get("행"), "글": node.get("글") or ""}
|
||||
if kind in ("빈", "글"):
|
||||
if src["글"].strip():
|
||||
if src["글"].strip().strip("'").strip():
|
||||
o.glyph(src["행"], src["글"])
|
||||
o.at[src["행"]]["차림"] = "글" # 812-3 줄 쌓기 글 줄 · 제목(`M02_Doc_NativeLayout`)
|
||||
elif src["글"].strip():
|
||||
o.glyph(src["행"], src["글"])
|
||||
elif kind == "대입":
|
||||
st.assign(node, src)
|
||||
|
||||
Reference in New Issue
Block a user