- 부모 저장 때 확인 안 된 하위 호표(깊이 끝까지)를 하위 먼저 그 체크대로 함께 만듦(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>
161 lines
7.7 KiB
Python
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["빠진줄"]}
|