Files
Aislo/M02_MasterTemplete/M02_Hopyo_Item.py
T
eomsangdonandClaude Opus 5.5 eaa8064f1a feat(777-1): 호표가 다른 호표를 부를 때 = 일위대가 둘 — 하위 함께 만들기 · 같은 SC 재사용 · 결과 글 · 납작 길 막음
- 부모 저장 때 확인 안 된 하위 호표(깊이 끝까지)를 하위 먼저 그 체크대로 함께 만듦(409 「먼저 확인」 걷음)
  · 서버 = 체크 제안 몸(M02_Hopyo_Kids.proposal ↔ 화면 bodyFor 짝) · 화면 = 확인 창 캐시 고름(Hopyo_Kids.ts)
  · 하위 실패 = 409 「하위 제N호표 실패 — …」 · 하위 · 하위남은줄 → 결과 표 [고르기]가 그 하위를 엶
  · [모두 고정] 연쇄 실패 까닭 따로 · 미리 보기는 하위 호표 줄을 이음으로 셈
- 같은 엑셀 다시 불러오기 = 같은 이름 · 같은 내용(수량까지) SC 재사용 — 본뜬.또(SK 꼴) · 끝내기는 그 몫만 뺌
- 결과 표 글 「SC-12 · 하위 제4호표 SC-11(사본 담음)」 · 확인 창 호표 줄 글 「하위 일위대가 SC-11 사본」
- [구조물 조합으로] 도 하위 먼저(못 만들면 지금처럼 고정 줄) · 붙여넣기 · B09 통째 = 이미 하위 먼저(안 고침)
- 엑셀 대조(CUTS · _excel_round · excel_given · compare) → 새 M02_Hopyo_Compare.py(Build 608 → 540)
- M02_Labor_Router 병행 흔적 가드 걷음(_later · inspect · 몸 칸 새로)
- STmate [엑셀 보기] 단가산출근거로 감(형식별 블록 시트 이름)
- 낡은 시험 고침: 「제46호표 먼저 확인」 409 · 글 → 하위 함께(777-1 정함) · 718-1 실패 = 남은줄 또는 하위 ·
  화면 시험 StaleError 안내 → unmadeKids · leftAt · 새 지킴 시험 test_777_1_hopyo_kids

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 09:24:48 +09:00

161 lines
7.7 KiB
Python

"""실무 호표 → 구조물 조합 항목 한 단계(PLAN 704-4 뒷부분 · `_제안_704-4_규격표.md` 6장 끝) — 사전조합 안 거침.
[구조물 조합으로] = 확인 창 몸(체크한 줄 · 통째 · `M02_Hopyo_Build.check` 그대로)으로 줄을 만들어(`build(save=False)` ·
사전조합 SC 문서 안 씀 · 옛 [사전조합 생성] 길은 그대로) 이 층 구조물집계표에 새 항목(SU · `M02_Item_Personal.create_item`
· 경로 「실무자료」) + 그 일위대가 문서에 줄을 담음(수량 = 1 단위 몫 `{손}`).
확인 안 된 하위 호표 = 먼저 일위대가로 함께 만듦(777-1 `M02_Hopyo_Kids.first` · 줄 = 하위 사본 · 못 만들면 고정 줄).
규격 묶음 = 같은 파일에서 이름 · 단위 같고 줄 구성(줄 이름 · 단위 차례) 같은 · 규격만 다른 호표(5-A 「구성 같은 묶음」) →
한 항목의 규격 열(704-4) — 열 이름 = 규격 글에서 달라지는 칸(「H=1.5」 → 이름 H · 열 1.5) · 아니면 이름 「규격」 ·
열마다 같은 몸으로 만든 줄 수량(줄 차례 같음 · 다르면 400) · 기본 열 = 연 호표.
"""
from __future__ import annotations
import re
from pathlib import Path
from typing import Any
from M02_MasterTemplete import M02_Hopyo_Build as hb
from M02_MasterTemplete import M02_Hopyo_Kids as hkids
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
from M02_MasterTemplete import M02_Item_Personal as personal
from M02_MasterTemplete import M02_Labor_Spec as spec
from M02_MasterTemplete import M02_Library_Ask as ask
from M02_MasterTemplete import M02_MasterTemplete_Store as store
PATH = ["실무자료"]
_TOKEN = re.compile(r"[\s,]+")
_PAIR = re.compile(r"^([A-Za-z가-힣Φφ]+)\s*[=:]?\s*([\d.]+)\D{0,3}$")
def _flat(text: Any) -> str:
return re.sub(r"\s+", "", str(text or ""))
def _shape(h: dict[str, Any]) -> tuple:
return tuple((_flat(x.get("이름")), _flat(x.get("단위"))) for x in h.get("줄") or [])
def columns(specs: list[str]) -> tuple[str, list[str]]:
"""규격 글들 → (규격 이름, 열 이름) — 처음 달라지는 칸이 모두 「H=1.5」 꼴이면 (H, [1.5 …]) ·
아니면 (「규격」, 그 칸 글) · 20 자 자름 · 겹치면 뒤에 차례."""
parts = [[t for t in _TOKEN.split(s.strip()) if t] for s in specs]
width = max((len(p) for p in parts), default=0)
at = next((i for i in range(width) if len({p[i] if i < len(p) else "" for p in parts}) > 1), 0)
cells = [p[at] if at < len(p) else "" for p in parts]
hits = [_PAIR.match(c) for c in cells]
if all(hits) and len({h.group(1) for h in hits}) == 1:
name, cols = hits[0].group(1), [h.group(2) for h in hits]
else:
name, cols = "규격", [c or s for c, s in zip(cells, specs, strict=True)]
out: list[str] = []
for c in cols:
c = (c.strip() or "열")[: spec.NAME_MAX]
while c in out:
c = f"{c[: spec.NAME_MAX - 2]}·{len(out) + 1}"
out.append(c)
return name[: spec.NAME_MAX], out
def siblings(source: Path, key: str, no: int) -> dict[str, Any]:
"""규격 묶음 제안 — 같은 이름 · 단위 · 줄 구성 · 규격 다른 호표(연 호표 먼저) →
`{묶음 [{번, 규격, 열}], 이름}` · 없으면 묶음 빈 것."""
units = hopyo_store.read(source, key).get("호표") or []
me = next((h for h in units if int(h["번"]) == int(no)), None)
if me is None:
raise store.StoreError(404, f"없는 호표 「제{no}호표」")
same = [
h
for h in units
if _flat(h.get("이름")) == _flat(me.get("이름"))
and _flat(h.get("단위")) == _flat(me.get("단위"))
and _shape(h) == _shape(me)
]
same.sort(key=lambda h: (int(h["번"]) != int(no), int(h["번"])))
specs = list(dict.fromkeys(_flat(h.get("규격")) for h in same))
if len(same) < 2 or len(specs) != len(same):
return {"묶음": [], "이름": None}
name, cols = columns([str(h.get("규격") or "") for h in same])
rows = [
{"번": int(h["번"]), "규격": h.get("규격") or "", "열": c}
for h, c in zip(same, cols, strict=True)
]
return {"묶음": rows, "이름": name}
def _doc(folder: Path, source: Path, key: str, no: int, lines: list, whole: Any, who: Any) -> dict:
try: # 777-1 — 하위 호표 먼저(엑셀 단가 한 줄로 납작 안 함) · 못 만들면 지금처럼 고정 줄
hkids.first(folder, source, key, no, who, lines) if whole is None else None
except store.StoreError:
pass
maker = hb.Builder(folder, source, key, no, who)
checked, pairs = maker.check(list(lines or []), whole)
return {**maker.build(checked, pairs, save=False), "호표": maker.hopyo}
def make_item(
folder: Path,
source: Path,
key: str,
no: int,
lines: list[dict[str, Any]] | None = None,
whole: dict[str, Any] | None = None,
bundle: list[int] | None = None,
name: str | None = None,
who: Any = None,
new: bool = False,
use: str | None = None,
) -> dict[str, Any]:
"""[구조물 조합으로] → `{key, 열, 이름, 줄수, 열수, 빠진줄}` —
`bundle` = 규격 열로 묶을 호표 번(연 호표 빼고) ·
이 층 같은 내용 항목 = 409(733-2) → `new` 그래도 새로 · `use` 그 항목(`씀` · 새로 안 만듦)."""
main = _doc(folder, source, key, no, lines, whole, who)
doc, hopyo = main["문서"], main["호표"]
title = (name or "").strip() or doc["이름"]
cols_doc = None
if bundle:
found = {r["번"]: r for r in siblings(source, key, no)["묶음"]}
want = [int(no), *(int(b) for b in bundle if int(b) != int(no))]
if any(b not in found for b in want):
raise store.StoreError(400, "규격 묶음은 이름 · 단위 · 줄 구성이 같은 호표만")
title = (name or "").strip() or str(hopyo.get("이름") or doc["이름"])
qty: dict[str, dict[str, Any]] = {}
for b in want:
got = (
main["문서"]
if b == int(no)
else _doc(folder, source, key, b, lines, whole, who)["문서"]
)
col = found[b]["열"]
for part in ("담은로직", "자재"):
mine, theirs = doc[part], got[part]
if len(mine) != len(theirs):
raise store.StoreError(400, f"제{b}호표 줄 구성이 다름 — 묶지 않고 따로 만듦")
for one, other in zip(mine, theirs, strict=True):
hand = (other.get("수량") or {}).get("손")
if hand is not None:
qty.setdefault(str(spec.line_key(one)), {})[col] = hand
cols_doc = {
"이름": siblings(source, key, no)["이름"],
"열": [found[b]["열"] for b in want],
"기본": found[int(no)]["열"],
"수량": qty,
}
count = {
"줄수": len(doc["담은로직"]) + len(doc["자재"]),
"열수": len(cols_doc["열"]) if cols_doc else 1,
}
want = {"담은로직": doc["담은로직"], "자재": doc["자재"], "단위": doc.get("단위")}
reuse = ask.ask_item(folder, {**want, "규격": cols_doc}, new, use)
if reuse:
return {**reuse, **count, "빠진줄": main["빠진줄"]}
with store.at(folder):
made = personal.create_item(folder, PATH, title, doc.get("단위"))
have = store.read("labor", made["열"])
body = {**have["문서"], "담은로직": doc["담은로직"], "자재": doc["자재"]}
body["단위"] = doc.get("단위") or body.get("단위")
if cols_doc:
body["규격"] = cols_doc
store.write("labor", made["열"], have["판"], body)
return {**made, **count, "빠진줄": main["빠진줄"]}