Files
Aislo/M02_MasterTemplete/M02_Doc_FromNative.py
T
eomsangdonandClaude Opus 5.5 ff19138727 feat(816-2): 후보 여럿 기계를 같은 파일 안 구성 대조로 가름
- 잣대 = 같은 파일 · 같은 때의 구성 줄(손료 계수 · 가격 · 주연료 · 조종원) — 우리 기계표 값과 맞대지 않음(요구 773-3)
- M02_Machine_Find.peers · narrow — 구성이 같은 단독 기계 이름 · 규격으로 다시 찾아 후보 가운데 키 하나면 이음 · 여럿 · 없음 = 그대로
- EST 원파일 MachMainDat 묶기(M02_San_Estx.makes) · EST 엑셀 「기계경비」 시트(M02_San_Xlsx.makes) → 자원 「같은구성」
- 저장 묶음(M02_Doc_FromNative) · 줄 만들기(M02_Doc_NativeEst · M02_San_Emit) 가 「같은구성」 을 넘김
- 앞서 짠 우리 표 값 대조는 걷음 · STC = 구성 표(BDQTY) 난독화로 없음 · STmate 엑셀 중기사용료 = 같은 파일 단독 짝 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 19:46:23 +09:00

219 lines
10 KiB
Python

"""경쟁사 원파일(.estx · .stc) → 가져온 식(PLAN 810-2 · 요구 705 · 706 · 서버 단독 · 원파일 우선).
읽기 = 810-1 파서(`M02_San_Estx` · `M02_San_Stc`) → 둘 꼴(`pack`)을 실무자료 산근 자리
(`M02_Sangeun_FromHopyo` · `실무자료/산근/<HP>.json` 칸 `원파일`)에 둠(원본 파일은 안 둠) →
블록 하나 = `{로직, 원본}`(`emit` · 793-2a 꼴 → 가져온 식 SK → `M02_Doc_Import.derive` 수식 문서):
EST = `M02_Doc_NativeEst`(구조 줄 표지로 정함) ·
STmate = 원문 그대로 `M02_San_Emit.stmate`(원파일 원문 = 엑셀 숨은 G 열 · 단가 = COSTN ·
끝전 = 옵션 SAN_TYP 1 비목버림 / 0 합버림_큰나머지 · 810-1 저장값 대조로 확인).
블록 번 = 엑셀 길과 같음(EST = 산근 표 차례 1부터 = 「단산N」 · STmate = 코드 숫자).
올리기 = 파일 하나(814-1 · 요구 742-1 — 같은 공사 엑셀 붙이기 걷음) · 원파일 = 단가산출표 판(`grid` ·
산출근거 머리) + 산출근거 · 일위대가 안 만듦(요구 743-2 · `hopyo` = 단산만).
"""
from __future__ import annotations
from decimal import Decimal
from typing import Any
from M02_MasterTemplete import M02_Doc_NativeEst as ne
from M02_MasterTemplete import M02_San_Emit as em
EXT = (".estx", ".stc")
RULE = {"0": "합버림_큰나머지"} # STmate SAN_TYP — 그 밖 = 비목버림
_EST_KEYS = ("행", "글", "보임", "품명", "단", "색인", "연결", "비목", "자원", "연결단가", "변수")
_EST_KEYS += ("줄참조", "결과", "합계", "저장")
_RES_KEYS = ("코드", "종류", "이름", "규격", "단위")
class NativeError(ValueError):
"""못 읽는 원파일."""
def is_native(name: str) -> bool:
return str(name or "").lower().endswith(EXT)
def read(name: str, data: bytes) -> dict[str, Any]:
"""원파일 → 둘 꼴(`pack`) · 못 읽음 = NativeError."""
from M02_MasterTemplete import M02_San_Estx as estx
from M02_MasterTemplete import M02_San_Stc as stc
try:
got = estx.read(data) if name.lower().endswith(".estx") else stc.read(data)
except Exception as e: # noqa: BLE001 — 파서 오류 = 사유 글
raise NativeError(f"원파일을 못 읽음 — {type(e).__name__}: {str(e)[:80]}") from e
return pack(got, name)
def pack(got: dict, name: str) -> dict[str, Any]:
"""파서 답 → 둘 꼴(셈 · 변환에 쓰는 칸만 · JSON)."""
out: dict[str, Any] = {"꼴": got["꼴"], "파일": name, "표지": got.get("표지") or []}
out |= {"엑셀": True} if got.get("엑셀") else {} # 812-1 엑셀 읽기기(`M02_San_Xlsx`) — 같은 꼴
head = ("코드", "번", "이름", "규격", "단위", "저장")
if got.get(
"엑셀"
): # 엑셀 읽기기 — 줄 행 = 시트 행(원문 카드 · 옛 엑셀 길과 같은 자리) · 블록 표지
head += ("표지", "몸", "꼬리")
got = {**got, "산근": [_rows(b) for b in got["산근"]]}
if got["꼴"] == "EST":
out["산근"] = [
{**{k: b.get(k) for k in head if k in b}, "줄": [_est_line(ln) for ln in b["줄"]]}
for b in got["산근"]
]
out["일위"] = [
{**{k: b.get(k) for k in head if k in b}, "줄": b["줄"]} for b in got["일위"]
]
return out
out["산근"] = [
{
**{k: b.get(k) for k in head if k in b},
"줄": [{"행": ln["행"], "글": ln["글"]} for ln in b["줄"]],
}
for b in got["산근"]
]
out["일위"] = [{k: b.get(k) for k in head if k in b} for b in got["일위"]]
res = got.get("자원") or {}
out["단가"] = {
code: {**p, **{k: (res.get(code) or {}).get(k) for k in ("이름", "규격", "단위")}}
for code, p in (got.get("단가") or {}).items()
}
out["끝전"] = RULE.get(str((got.get("옵션") or {}).get("SAN_TYP")), "비목버림")
return out
def _rows(b: dict) -> dict:
return {**b, "줄": [{**ln, "행": ln.get("엑셀행") or ln["행"]} for ln in b["줄"]]}
def _est_line(ln: dict) -> dict:
one = {k: ln.get(k) for k in _EST_KEYS if ln.get(k) not in (None, [], False, "")}
if one.get("자원"):
one["자원"] = {k: ln["자원"].get(k) for k in _RES_KEYS}
if ln["자원"].get("같은구성"): # 816-2 같은 파일에서 구성이 같은 단독 기계
one["자원"]["같은구성"] = ln["자원"]["같은구성"]
return one
# ── 블록 ──────────────────────────────────────────────────────────────
def numbers(saved: dict) -> dict[int, dict]:
"""블록 번 → 산근 블록(엑셀 길 번과 같음 · 엑셀 읽기기 EST = 시트 「N.」 번)."""
if saved["꼴"] == "EST":
return {int(b.get("번") or i): b for i, b in enumerate(saved["산근"], 1)}
return {int(str(b["코드"])[1:]): b for b in saved["산근"] if str(b["코드"])[1:].isdigit()}
def emit(saved: dict, no: int, keys: bool = True) -> dict[str, Any]:
"""원파일 산근 블록 하나 → `{로직, 원본}`(원본 `원파일` True · 코드 · 확정 수) · 없는 번 = KeyError."""
blk = numbers(saved)[int(no)]
if saved["꼴"] == "EST":
names = {b["코드"]: n for n, b in numbers(saved).items()}
out = ne.block(blk, int(no), keys, saved["파일"], names)
out["원본"] |= {"원코드": blk["코드"]} | ({"규격": blk["규격"]} if blk.get("규격") else {})
return _excel(saved, out, blk)
from M02_MasterTemplete import M02_San_Stmate as stm
nodes = [{"행": ln["행"], "글": ln["글"], **stm.parse_line(ln["글"])} for ln in blk["줄"]]
saved_amt = blk.get("저장") or {}
head = {"C": saved_amt.get("T"), "D": saved_amt.get("L"), "E": saved_amt.get("M")}
head["F"] = saved_amt.get("S")
block = {"코드": blk["코드"], "이름": blk.get("이름"), "머리": head}
block |= {"몸": blk.get("몸") or [], "꼬리": blk.get("꼬리") or []} # 엑셀 = 줄 금액 · 계 꼬리
out = em.stmate(block, nodes, saved["단가"], saved["끝전"], keys, saved["파일"])
marks = sum(1 for one in out["원본"]["글줄"] if one.get("표지"))
sure = len(out["로직"]["호표"]) + len(out["로직"].get("덧줄") or []) + len(out["로직"]["중간"])
out["원본"] |= {"원파일": True, "갈래": "STmate", "확정": {"확정": sure, "표지": marks}}
out["원본"] |= (
{"규격": blk["규격"]} if blk.get("규격") else {}
) # 연결 찾기 근거 · 이름 꼬리(814-1)
return _excel(saved, out, blk)
def _excel(saved: dict, out: dict, blk: dict) -> dict[str, Any]:
"""엑셀 읽기기 꼴(812-1) — 원본 `원파일` 거짓 · `엑셀` 참(화면 꼬리표 「엑셀」 · 원코드 = 단산 번)."""
if saved.get("엑셀"):
out["원본"] |= {"원파일": False, "엑셀": True}
out["원본"] |= {"시트": "단가산출"} if saved["꼴"] == "EST" else {}
out["원본"].pop("원코드", None)
out["원본"]["표지"] += [m for m in blk.get("표지") or [] if m.startswith("참고 · ")]
return out
def inner(saved: dict, logic: dict) -> list[tuple[dict, int | None, str | None]]:
"""안쪽 산근 줄 `[(호표 줄, 같은 원파일 블록 번, 못 잇는 까닭)]`(`FromHopyo._inner` 짝)."""
have = numbers(saved)
by_code = {str(b.get("코드")): n for n, b in have.items()}
out = []
for line in logic["호표"]:
res = line.get("자원") or {}
if res.get("갈래") != "단가산출":
continue
n = by_code.get(str(res.get("코드")))
if n is None:
out.append((line, None, "같은 원파일에 안쪽 블록 없음"))
elif res.get("옮김"):
out.append((line, n, "비목이 바뀐 부름"))
else:
out.append((line, n, None))
return out
# ── 단가산출표 판(814-1 · 요구 742-2 · 743-2) ───────────────────────────
LIST = "단가산출표"
_LIST_HEAD = ("코드", "이름", "규격", "단위", "재료비", "노무비", "경비", "합계")
_LIST_COST = ("M", "L", "S", "T")
def hopyo(saved: dict) -> dict[str, Any]:
"""원파일 → 실무자료 읽기 꼴 — 단산(산출근거 머리)만 · 일위대가 안 만듦(요구 743-2 · HeadMainDat 안 씀)."""
out: dict[str, Any] = {"꼴": "원파일", "시트": saved["꼴"], "호표": [], "검산": {"어긋남": []}}
out["단산"] = [
{"번": n, "이름": b.get("이름"), "단위": b.get("단위"), "금액": _cost(b.get("저장"))}
for n, b in numbers(saved).items()
]
return out
def grid(saved: dict) -> dict[str, Any]:
"""원파일 → 단가산출표 한 시트 격자(`M02_Hopyo_Grid.read` 꼴) — 산출근거 머리(코드 · 이름 · 규격 ·
단위 · 저장 금액) 한 줄씩 · 행 = 1 머리 + 블록 차례."""
cells: dict[str, dict] = {}
cols = "ABCDEFGH"
for c, head in zip(cols, _LIST_HEAD):
cells[f"{c}1"] = {"값": head}
rows = []
for at, (n, b) in enumerate(numbers(saved).items(), 2):
cost = b.get("저장") or {}
vals = [b.get("코드"), b.get("이름"), b.get("규격"), b.get("단위")]
vals += [_dec(cost.get(k)) for k in _LIST_COST]
for c, v in zip(cols, vals):
if v not in (None, ""):
cells[f"{c}{at}"] = {"값": v if isinstance(v, (int, float)) else str(v)}
rows.append({"번": n, "이름": b.get("이름"), "행": [at, at]})
sheet = {"id": "s1", "이름": LIST, "칸": cells, "틀고정": {"행": 1}}
sheet["열"] = {c: {"폭": w} for c, w in zip(cols, (10, 40, 18, 6, 12, 12, 12, 12))}
book = {"종류": "통합문서", "판": 1, "열": "", "서식": [{}], "시트": [sheet], "활성": "s1"}
return {
"책": book,
"호표": [],
"단산": rows,
"보기": {LIST: "s1"},
"원본": {LIST: LIST},
"목록": {"호표": [], "단산": [{"번": r["번"], "행": r["행"][0]} for r in rows]},
"줄": [],
}
def _dec(v: Any) -> int | float | None:
"""수 → 엑셀 읽기와 같은 꼴(정수 = int · 아니면 float · 없음 = None)."""
if v is None:
return None
d = Decimal(str(v))
return int(d) if d == d.to_integral_value() else float(d)
def _cost(d: dict | None) -> dict[str, Any]:
d = d or {}
out = {"계": _dec(d.get("T"))}
return out | {name: _dec(d.get(c)) for c, name in em.COMP.items()}