Files
Aislo/M02_MasterTemplete/M02_Doc_Import_Shape.py
T
eomsangdonandClaude Opus 5.5 ae8d098a10 fix(815-3): 합친 뒤 시험 셋 가름 · 시간당 경비 식 조종원 차례 · 호표 기계 글 머리 꼬리 · 합친 판 요약 다시 잼
- 809_1 이름 = 진짜 흠 + 낡은 기대 — 읽기기(812-1)가 지은 율 줄 이름 「율」이 엑셀 글 머리로 안 바뀜 → `Import_Shape._MADE` 에 율 더함 · 합계 = 차림 글줄 · 기계 = 한 줄(이름 · 규격 칸) 은 낡은 기대 고침 · 금액 15,935 · 336 · 723 · 16,994 그대로
- 812_9 별칭 = 낡은 기대 — 815-2 가 아스팔트페이버 별칭을 마스터 기계 줄 칸으로 옮겨 「넣기 전 못 찾음」 이 안 맞음 · 시험은 마스터 칸으로 봄 · 부르는 곳 없는 `M02_Machine_Find.learn`(옛 장부 쓰기) · `by_code` · `same_kind`(걷은 연결 찾기 몫) 지움
- 804_2 hourly = 낡은 기대 — 814-1 이 GC000268 목록을 117 → 209 로 늘려 겹친 116 대 고름이 GC000269 로 뒤집힘(116 대 모두 원 단위 같음) · 같은 로직 줄만 대던 시험 → 같은 갈래 식(부착 · 손료만 · 조종원) 줄 모두 댐
- 진짜 흠(브레인 갈래 가) — GC000272 「조종원 없는 기종」 목록에 기계표 조종원 1 인 9 대가 겹쳐 목록 길이 차례로 고름 → `logic_of` = 기계표 조종원 > 0 이면 조종원 호표 든 식 먼저 · 불러오기 `hourly_link` 도 같은 길 · 바뀐 4 대 = 진동롤러(핸드가이드식) 0.7ton 6,557 → 41,969 · 래머 80kg 1,959 → 23,708 · 플레이트 콤팩터 1.5ton 2,855 → 24,604 · 발전기 100kW 45,535 → 67,284(M01 문서 금액 그대로) · 시험에 9 대 굳힘
- 778_5 기계 표 = 진짜 흠 — 812-9 찾기가 호표 기계 글 「운 반 : 덤프트럭(4.5Ton)」 · 「…[hr] : 90%」 · 「(90%)」 머리 꼬리를 안 뗌 → `Hopyo_Fixed._eqs` 쌍점 조각 · 율 꼬리 뗌 · 이어짐 187 · 고를 것 1 · 못 이음 2(조합 한 줄 · 괄호 깨짐) · 모두 이음 블록 161
- 721 단추 자리 = 낡은 기대(808-1 [단가 갱신][재생성] tools 더함)
- 815-1 보고 요약에 「합친 뒤」 열 — 기계 이음 95.7 % · 우리 값 원파일 1,891 · 0 원 줄 블록 147

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 16:32:28 +09:00

232 lines
10 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 = ("노무비", "재료비", "경비")
# 지은 이름 — 「줄_2 · 노무비_2」 · 읽기기 율 줄 「율」
_MADE = re.compile(r"^(?:(?:줄|노무비|재료비|경비|계|소계|합계)?_\d+|율(?:_\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, who: dict[str, str] | None = None) -> list[int] | None:
"""at 에서 시작하는 기계 세 줄(노무비 · 재료비 · 경비 · 차례 아무렇게) — 같은 기계 이음 · 같은 수량 ·
시간당 경비 이음 없음(숫자)이면 같은 기계 자원(`who` 줄 이름 → 자원 · 814-1)."""
money = doc.get("금액") or []
three = money[at : at + 3]
if len(three) < 3 or sorted(str(x.get("비목")) for x in three) != sorted(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 all(x.get("갈래") == "숫자" and not isinstance(x.get("값"), dict) for x in links):
mine = {(who or {}).get(str(x.get("이름"))) for x in three}
if len(mine) != 1 or None in mine:
return None
elif any(x.get("갈래") != "단가산출" or "기계" not in (x.get("입력") or {}) for x in links):
return None
elif len({(x.get("키"), str(x.get("입력"))) for x in links}) != 1 or [
x.get("칸") for x in links
] != [x.get("비목") for x in three]:
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)
AFTER = "_뒤" # 옮긴 조합 다음 줄 — 차림이 원본 행 차례로 다시 섞지 않게(다듬기 사이 임시)
def regroup(doc: dict, who: dict[str, str], parts: set[str]) -> None:
"""814-1 — 조합 기계 비목 세 줄 사이에 낀 다음 줄(값 0)을 세 줄 뒤로 · 같은 기계끼리 모음
(첫 줄 · 다음 줄이 엇갈려 항목이 겹치지 않게 · 값 0 줄이라 셈 그대로). `parts` = 조합 다음 줄 이름."""
money = doc.get("금액") or []
at = 0
while at < len(money):
head = who.get(str(money[at].get("이름")))
if head is None or str(money[at].get("이름")) in parts:
at += 1
continue
end, mains, rest = at, [], []
while end < len(money):
name = str(money[end].get("이름"))
if name in parts:
rest.append(money[end])
elif who.get(name) == head and len(mains) < 3:
mains.append(money[end])
else:
break
end += 1
if len(mains) > 1 and rest:
seen = list(dict.fromkeys(who.get(str(x.get("이름"))) for x in rest))
rest.sort(key=lambda x: seen.index(who.get(str(x.get("이름"))))) # 같은 기계끼리
for x in rest:
x[AFTER] = True # 차림(`NativeLayout.apply`) 원본 행 = 앞 줄 행 뒤
money[at:end] = mains + rest
at = max(end, at + 1)
def machines(doc: dict, title: dict[str, str], who: dict[str, str] | None = None) -> None:
"""② 기계 세 줄 → 한 기계 줄(비목 묶음) · 부르던 식 = 비목 칸. `title` = {줄 이름: 자원 이름} ·
`who` = {줄 이름: 기계 자원 열쇠}(시간당 경비 이음 없는 세 줄 · 814-1)."""
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, who)
if idx is None:
out.append(money[at])
at += 1
continue
three = sorted((money[i] for i in idx), key=lambda x: COSTS.index(x["비목"]))
first = vars_[str(money[idx[0]]["단가"])]
if first["연결"].get("갈래") == "숫자": # 시간당 경비 이음 없음 = 엑셀 값 묶음
link = {
"갈래": "숫자",
"값": {c: (vars_[str(x["단가"])]["연결"]).get("값") for c, x in zip(COSTS, three)},
}
else:
link = {k: v for k, v in first["연결"].items() if k not in ("칸", "엑셀")}
link["엑셀"] = {
c: Decimal(str((vars_[str(x["단가"])]["연결"]).get("엑셀") or 0))
for c, x in zip(COSTS, three)
}
lead = money[idx[0]]
name = title.get(str(lead.get("이름"))) or str(lead.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 lead.items() if k != "기호"} | {"이름": name, "비목": "묶음"}
line["단가"] = lead["단가"]
_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} - {str(lead["단가"])}
]
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, who, parts = {}, {}, set()
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)
key = (res.get("코드"), res.get("이름"), res.get("규격"))
who[str(item.get("이름"))] = repr(key)
if res.get("조합"):
parts.add(str(item.get("이름")))
regroup(doc, who, parts)
machines(doc, title, who)
lay.apply(doc, meta)
for line in doc.get("금액") or []:
line.pop(AFTER, None)
return doc