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:
2026-11-09 07:21:35 +09:00
co-authored by Claude Opus 5.5
parent 5531e5f39f
commit 624fd3a555
10 changed files with 759 additions and 7 deletions
+22
View File
@@ -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)