Files
Aislo/M02_MasterTemplete/M02_Doc_Import_Shape.py
T
eomsangdonandClaude Opus 5.5 624fd3a555 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>
2026-11-09 07:21:35 +09:00

177 lines
7.7 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.
"""M02 가져온 식 수식 문서 다듬기(PLAN 809-1 · 요구 704) — `M02_Doc_Import.derive` 끝에 부름 · 금액 그대로.
① 소계 · 합계 성격 줄(덧줄 · 단위 「식」 · 수량 없음 · 단가 = 다른 줄 기호 합) 이름 = 엑셀 원 글 머리
(`가져온.원본.글줄` 자리 = 그 줄 이름 · 「:」 앞 글 · 글이 수식뿐이면 바로 위 설명 글 ·
「합 계」 절 아래 비목 = 「노무비 합계」 꼴) — 「줄_2 · 노무비_2 · 계_2」 같은 지은 이름 걷음
② EST 기계 세 줄(같은 자원 · 같은 기계 이음 · 같은 수량 · 노무비 · 재료비 · 경비 하나씩) = 한 기계 줄
(이름 = 자원 이름 · 비목 묶음 · 단가 = 기계 이음 칸 없음 · 엑셀 = 값묶음) · 세 줄 기호를 부르던 식 =
그 줄 비목 칸(`C_크레인.노무비`) — 셈 값 그대로(줄 끝수 = 비목마다 같은 규칙)
엑셀 원 글 · 엔진 줄은 안 고침(문서 겉꼴만) · 못 맞추면 그대로 둠.
"""
from __future__ import annotations
import re
from decimal import Decimal
from typing import Any
COSTS = ("노무비", "재료비", "경비")
_MADE = re.compile(r"^(?:줄|노무비|재료비|경비|계|소계|합계)?_\d+$")
_SECTION = re.compile(r"^\s*\d+\s*\.\s*(.+?)\s*$")
def _flat(text: Any) -> str:
return re.sub(r"\s+", "", str(text or ""))
def _head(text: str) -> str:
"""「노무비 : 13,944.5 + …」 → 「노무비」 · 머리 없음 = 빈 글."""
head = text.split(":", 1)[0] if ":" in text else ""
return _flat(head)
def names(doc: dict, lines: list[dict]) -> None:
"""① 지은 이름(「줄_2 · 노무비_2 · 계_2」) → 엑셀 원 글 머리."""
section, note, by = "", "", {}
for one in lines:
text = str(one.get("글") or "")
if hit := _SECTION.match(text):
section, note = _flat(hit.group(1)), ""
continue
spot = one.get("자리")
if not spot:
note = text.strip()
continue
head = _head(text) or note
if head and section in ("합계", "계") and head in COSTS:
head = f"{head} 합계"
by[str(spot)] = head
note = ""
taken = {str(x.get("이름")) for x in doc.get("금액") or []}
for line in doc.get("금액") or []:
name = str(line.get("이름") or "")
new = by.get(name)
if _MADE.match(name) and new and new not in taken: # 단계 · 다른 줄 이름과 안 겹칠 때만
line["이름"] = new
taken.add(new)
_rename_steps(doc, {name: new})
def _rename_steps(doc: dict, swap: dict[str, str]) -> None:
"""단계 줄 목록(이름으로 묶음) — 바뀐 이름 따라감 · 합친 줄 = 한 번."""
for st in doc.get("단계") or []:
got = []
for n in st.get("줄") or []:
n = swap.get(n, n)
if n not in got:
got.append(n)
st["줄"] = got
def _bundle_of(doc: dict, at: int) -> list[int] | None:
"""at 에서 시작하는 기계 세 줄(노무비 · 재료비 · 경비) — 같은 기계 이음 · 같은 수량."""
money = doc.get("금액") or []
three = money[at : at + 3]
if len(three) < 3 or [x.get("비목") for x in three] != list(COSTS):
return None
vars_ = {v.get("열쇠"): v for v in doc.get("변수") or []}
links = [(vars_.get(str(x.get("단가"))) or {}).get("연결") or {} for x in three]
if any(x.get("갈래") != "단가산출" or "기계" not in (x.get("입력") or {}) for x in links):
return None
same = {(x.get("키"), str(x.get("입력"))) for x in links}
if len(same) != 1 or [x.get("칸") for x in links] != list(COSTS):
return None
for k in ("수량", "규격", "자리", "합밖"):
if len({str(x.get(k) or "") for x in three}) != 1:
return None
names = {str(x.get("이름")) for x in three}
for st in doc.get("단계") or []: # 단계가 셋 중 일부만 담으면 못 합침
if 0 < len(names & set(st.get("줄") or [])) < 3:
return None
return [at, at + 1, at + 2]
def _place(doc: dict, name: str, used: set[str]) -> str:
"""기계 줄 번지 — 자원 이름 첫 낱말(괄호 · 규격 · 기호 뺌) · 겹치면 숫자."""
word = re.sub(r"[^\w]|_", "", re.split(r"[\s(]", name.strip())[0]) or "기계"
if word[0].isdigit():
word = f"기계{word}"
have = used | {str(x.get("번지")) for x in doc.get("금액") or [] if x.get("번지")}
got, n = word, 1
while got in have:
n += 1
got = f"{word}{n}"
used.add(got)
return got
def _rewrite(text: Any, swap: dict[str, str]) -> Any:
if not isinstance(text, str) or not swap:
return text
keys = "|".join(re.escape(k) for k in sorted(swap, key=len, reverse=True))
pat = re.compile(rf"(?<![^\W_])(?:{keys})(?![^\W_]|[′'.])")
return pat.sub(lambda m: swap[m.group(0)], text)
def machines(doc: dict, title: dict[str, str]) -> None:
"""② EST 기계 세 줄 → 한 기계 줄(비목 묶음) · 부르던 식 = 비목 칸. `title` = {줄 이름: 자원 이름}."""
money = doc.get("금액") or []
vars_ = {v.get("열쇠"): v for v in doc.get("변수") or []}
out, swap, at, used = [], {}, 0, set()
while at < len(money):
idx = _bundle_of(doc, at)
if idx is None:
out.append(money[at])
at += 1
continue
three = [money[i] for i in idx]
first = vars_[str(three[0]["단가"])]
link = {k: v for k, v in first["연결"].items() if k not in ("칸", "엑셀")}
excel = {
c: Decimal(str((vars_[str(x["단가"])]["연결"]).get("엑셀") or 0))
for c, x in zip(COSTS, three)
}
link["엑셀"] = excel
name = title.get(str(three[0].get("이름"))) or str(three[0].get("이름"))
first["연결"], first["이름"] = link, f"{name} 단가"
if name in {str(x.get("이름")) for x in money} - {str(x.get("이름")) for x in three}:
name = f"{name} ({at + 1})" # 같은 이름 줄이 있으면 겹치지 않게
line = {k: v for k, v in three[0].items() if k != "기호"} | {"이름": name, "비목": "묶음"}
_rename_steps(doc, {str(x.get("이름")): name for x in three})
if any(x.get("기호") for x in three): # 부르던 식 = 번지 칸(엔진 `번지.X.노무비`)
place = _place(doc, name, used)
line["번지"] = place
for c, x in zip(COSTS, three):
if x.get("기호"):
swap[str(x["기호"])] = f"번지.{place}.{c}"
doc["변수"] = [
v
for v in doc["변수"]
if v is first or v.get("열쇠") not in {str(x["단가"]) for x in three[1:]}
]
out.append(line)
at += 3
doc["금액"] = out
for line in out:
for k in ("단가", "수량"):
line[k] = _rewrite(line.get(k), swap)
for one in doc.get("식") or []:
one["식"] = _rewrite(one.get("식"), swap)
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 []:
res = item.get("자원") or {}
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