Files
Aislo/resources/master_data/scripts/master_material_combo.py
eomsangdonandClaude Opus 5.5 4abfc5b265 refactor(M02): 자재 조합 표를 마스터 템플릿으로 옮김 · M02 좌측 「자재 조합」 컨테이너
- 표 resources/master_data/자재조합.json → resources/master_template/자재조합.json(git mv) · 키 UM 은 표 안 「다음」 번호 · _키대장 · master_keys 의 UM 뺌 · check_master 틀이 새 자리를 같이 봄
- 서버: M01 /api/m01/matset* 뺌 · M02 저장소 M02_MasterTemplete_Matset.py(목록 · 새로 · 고치기 · 지우기 · 쓰는 열 검사) · 길 GET/POST /api/m02/material-combos · PUT/DELETE /api/m02/material-combos/{키} · 갈라 내기(branchMaterialCombo)도 이 저장소로
- 화면: M01 좌측 「자재 조합」 컨테이너 뺌 · M02 좌측 「자재 조합」 컨테이너(찾기 · 키 맨 앞 목록 · 새로 · 본떠 만들기 · 지우기) · 메인 칸 상세(이름 · 비고 · 본뜬키 · 담은 자재 표 · 자재 더하기 · 빼기 · 저장) · 쓰는 열이 있으면 지우기는 확인 창 없이 알림
- 시험: test_m01_matset → test_m02_matset · test_m02_combo_branch 사본 자리 · 실제 자재 코드

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VwJQ5eRrCKf3nH9EFeKvJz
2026-09-28 22:25:30 +09:00

85 lines
3.9 KiB
Python

# -*- coding: utf-8 -*-
"""자재 조합 — 구조물 열 재료산출이 쓰는 자재 목록 묶음(키 UM · `_틀.md` 11장).
자리는 마스터 템플릿 `resources/master_template/자재조합.json`(구조물집계표조합.json 과 같은 결) ·
키는 표 안 「다음」 번호(`_키대장` 밖). 담은 자재 목록만 지님(수량 · 계산은 재료산출 몫) ·
갈라 내기(M02) 때 새 줄 · 본뜬키 = 원래 키. 저장 · 화면은 M02(`M02_MasterTemplete_Matset.py`).
"""
from __future__ import annotations
import json
from pathlib import Path
import master_combo as mcb
import master_keys as mk
GROUP = "자재조합"
FILE = "자재조합.json"
TID = "UM"
OWNERS = mcb.OWNERS
MAX_이름 = mcb.MAX_이름
PATH = Path(__file__).resolve().parents[2] / "master_template" / FILE
COLS = ("키", "이름", "담은자재", "본뜬키", "소유", "비고")
ITEM_COLS = ("코드", "이름", "규격", "단위")
def items(row: dict) -> list[dict]:
got = row.get("담은자재")
return got if isinstance(got, list) else []
def check_form(name: str, data: dict) -> list[str]:
"""파일 틀 + 줄 — 키 모양 · 열 한 벌 · 이름 · 소유 · 담은 자재(빈 목록 · 모양 · 같은 코드 두 번)."""
out = [] if data.get("그룹") == GROUP else [f"{name} · 머리 그룹이 「{GROUP}」 아님"]
rows = data.get("줄")
if not isinstance(rows, list):
return out + [f"{name} · 머리 「줄」 목록 없음"]
after = data.get("다음")
if not isinstance(after, int) or after < 1:
out.append(f"{name} · 머리 「다음」 이 1 이상 정수 아님")
after = None
seen_names: dict[str, str] = {}
for row in rows:
key = str(row.get("키"))
where = f"{name} · {key} {row.get('이름') or ''}".strip()
if not mk.KEY.fullmatch(key) or key[:2] != TID:
out.append(f"{where} · 키 모양이 「{TID}」 + 6자리 아님")
elif after and int(key[2:]) >= after:
out.append(f"{where} · 키 번호가 머리 「다음」({after}) 보다 작지 않음")
if tuple(row) != COLS:
out.append(f"{where} · 열이 한 벌이 아님 — 「{' · '.join(COLS)}」")
plain = row.get("이름").strip() if isinstance(row.get("이름"), str) else ""
if not plain or len(plain) > MAX_이름:
out.append(f"{where} · 「이름」 이 없거나 {MAX_이름}자 넘음")
elif plain in seen_names:
out.append(f"{where} · 「이름」 이 같은 조합과 겹침 — {seen_names[plain]}")
seen_names.setdefault(plain, key)
if row.get("소유") not in OWNERS:
out.append(f"{where} · 소유 「{row.get('소유')}」 — {' · '.join(OWNERS)} 아님")
origin = row.get("본뜬키")
if origin is not None and not (mk.KEY.fullmatch(str(origin)) and origin[:2] == TID):
out.append(f"{where} · 본뜬키 모양 「{origin}」")
got = items(row)
if not got:
out.append(f"{where} · 빈 조합 — 담은 자재가 없음")
codes: set[str] = set()
for at, one in enumerate(got, start=1):
if not isinstance(one, dict) or set(one) - set(ITEM_COLS):
out.append(f"{where} 줄{at} · 담은 자재 줄 모양 「{one}」")
continue
code = str(one.get("코드") or "")
if not mk.KEY.fullmatch(code):
out.append(f"{where} 줄{at} · 자재 코드 모양 아님 「{code}」")
elif code in codes:
out.append(f"{where} 줄{at} · 같은 자재를 두 번 담음 「{code}」")
codes.add(code)
return out
def check_file(path: Path = PATH) -> list[str]:
"""마스터 템플릿 자재 조합 표 — 없으면 한 줄."""
if not path.is_file():
return [f"{path.name} · 마스터 템플릿에 없음"]
return check_form(path.name, json.loads(path.read_text(encoding="utf-8")))