Files
Aislo/M02_MasterTemplete/M02_Hopyo_Item.py
T
eomsangdonandClaude Opus 5.5 f9ffd5fc77 feat(M02 사전조합 → 구조물 조합 한 단계): 실무자료 확인 창 [구조물 조합으로] · 규격 묶음 제안 (PLAN 704-4 뒷부분)
- 확인 창 몸 그대로(체크 = 품셈 줄 · 나머지 = 고정 줄)로 구조물 조합 항목(SU)을 바로 만듦 — 사전조합 SC 문서 안 씀 · 옛 [사전조합 생성] 길 그대로
- 규격 묶음 제안 — 같은 이름 · 단위 · 줄 구성 · 규격만 다른 호표 → 한 항목의 규격 열(H=1.5 → 이름 H · 열 1.5) · 열마다 같은 몸으로 만든 줄 수량 · 기본 열 = 연 호표
- 서버 M02_Hopyo_Item · /hopyo/{키}/{번}/siblings · /hopyo-item(이 층 쓰기 · 「실무자료」 폴더) · Builder.build(save=False)
- 규격 열 줄 id 가 고정 줄을 못 알아보던 흠 — line_key = fixed_id(TS lineId 와 같음)
- 만든 뒤 나무 · 옆 목록 다시
- 시험: 울진 대흥 돌기슭막이 H 1.5 / 2.0 / 2.5 = 한 항목 열 셋 · 열마다 = 그 호표 엑셀 합계 · 줄 구성 다른 호표 400 · 남의 층 403 · 사전조합 0 · 마스터 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:14:59 +09:00

146 lines
6.8 KiB
Python

"""실무 호표 → 구조물 조합 항목 한 단계(PLAN 704-4 뒷부분 · `_제안_704-4_규격표.md` 6장 끝) — 사전조합 안 거침.
[구조물 조합으로] = 확인 창 몸(체크한 줄 · 통째 · `M02_Hopyo_Build.check` 그대로)으로 줄을 만들어(`build(save=False)` ·
사전조합 SC 문서 안 씀 · 옛 [사전조합 생성] 길은 그대로) 이 층 구조물집계표에 새 항목(SU · `M02_Item_Personal.create_item`
· 경로 「실무자료」) + 그 일위대가 문서에 줄을 담음(수량 = 1 단위 몫 `{손}`).
규격 묶음 = 같은 파일에서 이름 · 단위 같고 줄 구성(줄 이름 · 단위 차례) 같은 · 규격만 다른 호표(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_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_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:
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,
) -> dict[str, Any]:
"""[구조물 조합으로] → `{key, 열, 이름, 줄수, 열수, 빠진줄}` — `bundle` = 규격 열로 묶을 호표 번(연 호표 빼고)."""
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,
}
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)
count = {
"줄수": len(doc["담은로직"]) + len(doc["자재"]),
"열수": len(cols_doc["열"]) if cols_doc else 1,
}
return {**made, **count, "빠진줄": main["빠진줄"]}