# -*- 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")))