Files
Aislo/M02_MasterTemplete/M02_Doc_Import_Shape.py
T
eomsangdonandClaude Opus 5.5 bed35e3bc3 feat(814-1 3·4·6·7·8·9·10): 불러오기 결정 반영 — 세 줄 묶기 · 꼬리 뗀 맞대기 · 유가 · 재료 · 까닭 · 가벼움
- 3 연결 찾기 = 엑셀 이름 규격 · 단위 꼬리 뗀 이름으로 맞댐(`M02_Doc_Match_Note.bare` · 보이는 이름 그대로) · STmate 블록 규격도 가져온 칸에
- 4 기계 세 줄 = 기계 한 줄 — STmate X 코드 노 · 재 · 경 세 참조 줄(같은 수량 · 번지) 묶음(묶어서 셈 못 하는 세 줄만 그대로) ·
  문서 단계 묶기 넓힘(차례 아무렇게 · 시간당 경비 이음 없으면 엑셀 값 묶음 · 번지 같을 때) · 조합 다음 줄 = 세 줄 뒤 · 같은 기계 한 항목
- 6 공구손료 율 비목 차이(재료비 ↔ 경비) = 참고(`M02_Doc_Match_Value.tool_cost`) — 확정 안 막음
- 7 못 이은 줄 까닭(`M02_San_Link.why` — 자재 없음 · 운임표 없음 · 기계 표에 없음 · 별칭 대기 · 후보 여럿 · 시간당 경비 없음 · 조합) =
  문서 설명 · 이음 없는 자원 띠 · 연결 「없음」 띠 = 「우리 기초 없음 — 우리 정보로 셈 n/m 줄」 · 형제만 = 「조합」
- 8 유가 = 전국평균 줄 + 지역 옵션(`MO:경유:{S_지역}`) · 전력 = 일반용(갑)Ⅰ 저압 + 계약종별 옵션 · 엑셀 값 = 검산 칸
- 9 재료 = 지역 꼬리 뗀 이름 · 규격 정확 일치면 이음(지역 여럿 = 줄 지역 › 서울)
- 10 조합 첫 줄 규격 = 그 기계 · 얼린 줄도 소계 식에(북부청임도 33) · 기계 줄 찾기 판정 늘 붙음 · 올린 직후 409(HP 보이기 전 산근 보관) ·
  엑셀 블록 「엑셀 읽기 확정」 글 · 전수 도우미 str() 뗌 · 줄 쌓기 이름에 규격이 들면 한 번만
- M02_San_Link 새 파일(San_Emit 700 줄 막음 — 요소 잇기 · 시간당 · 조합 · 까닭)
- 시험: 새 test_814_1 · 812-2 · 810-4 거푸집 41 = 근접 → 확정(공구손료 비목 = 값 · 요구 744 C1) · 바꾸기 거절 견본 = 19 ·
  812-9 조합 첫 줄 규격 「0.7+브레이카」 → 「0.7」(칩 글 · 요구 738 흠 12)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:47:08 +09:00

231 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 = ("노무비", "재료비", "경비")
_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, 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