Files
Aislo/B08_Quantity/B08_Quantity_Engine_StructureLibrary.py
T
eomsangdonandClaude Opus 5 4f1521eea9 feat(b08): 구조물도 양식 가져오기 — 3단 목록은 고를 때만, 표는 프로젝트에 박힌 양식만 읽음
- 라이브러리 항목을 목록으로(항목마다 AX-ST-<8hex>.json) · 기본 찰쌓기에 코드 부여
- 가져오기: 로그인한 사람의 개인·회사 단 + 프로그램 기본에서 골라 {프로젝트}/B08_Quantity/library 에 박음 · 그 종류의 고친 식은 비움
- 표·원단위·자재총괄·인계는 박힌 양식 → 없으면 프로그램 기본만 읽음(여는 사람마다 값이 안 갈림)
- [확정] 때 아직 안 박힌 기본 양식을 박음 · 장 머리에 「어느 단에서 가져왔나 · 고친 식 N줄」
- 시험 test_b08_structure_library.py 6개 · 전체 1524 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
2026-09-13 18:21:19 +09:00

112 lines
4.8 KiB
Python

"""구조물도 양식 **라이브러리** — 3단(개인·회사·프로그램) 목록과 프로젝트로 **가져오기** (PLAN 4장).
⛔ 표를 그릴 때 라이브러리를 읽지 않음 — 가져온 양식은 **프로젝트 작업본에 박히고**
(`{프로젝트}/B08_Quantity/library/<code>.json`) 표는 그것만 읽음. 3단은 가져오기 고르개에서만 돎.
여는 사람마다 값이 갈리지 않게 하려는 것(브레인 판정 Ⓑ — 개인 = 로그인한 사람).
⚠ 아직 아무것도 안 가져온 종류는 프로그램 기본을 읽음 — 보는 사람과 무관한 한 벌.
⚠ 저장은 **목록**(항목마다 파일 한 개 · 코드 `AX-ST-<8자리 16진>`, 명세 2장 ②) —
지금은 종류당 하나만 씀(판정 Ⓐ). 고르기 칸은 뒤에 붙임.
"""
from __future__ import annotations
import json
import re
from pathlib import Path
from typing import Any
from B08_Quantity.B08_Quantity_Engine_StructureTemplate import TEMPLATE_DIR
from config.config_system import STORAGE_BASE_DIR
#: 단 이름 — 고르개에 이 차례로 보임(가까운 것부터).
TIERS = ("personal", "company", "program")
#: 항목 코드 — 파일 이름이 되므로 모양을 먼저 봄(경로 벗어남 막이).
CODE_PATTERN = re.compile(r"^AX-ST-[0-9a-f]{8}$")
LIBRARY_SUBDIR = "library"
def tier_dirs(company_id: Any, user_id: Any) -> dict[str, Path]:
"""로그인한 사람의 3단 폴더. 회사가 없으면(시스템 관리자) 개인·회사 단은 없음."""
dirs: dict[str, Path] = {}
if company_id is not None:
company = Path(STORAGE_BASE_DIR).resolve() / str(company_id)
if user_id is not None:
dirs["personal"] = company / str(user_id) / LIBRARY_SUBDIR
dirs["company"] = company / LIBRARY_SUBDIR
dirs["program"] = TEMPLATE_DIR
return dirs
def _items(folder: Path) -> list[dict[str, Any]]:
if not folder.is_dir():
return []
return [json.loads(p.read_text(encoding="utf-8")) for p in sorted(folder.glob("*.json"))]
def list_items(dirs: dict[str, Path], type_id: str) -> list[dict[str, Any]]:
"""가져오기 고르개 — 단마다 그 종류의 항목(단·코드·이름)."""
return [
{"tier": tier, "code": item.get("code"), "name": item.get("name"), "type_id": type_id}
for tier in TIERS
if tier in dirs
for item in _items(dirs[tier])
if item.get("type_id") == type_id and item.get("code")
]
def find_item(dirs: dict[str, Path], tier: str, code: str) -> dict[str, Any] | None:
folder = dirs.get(tier)
if folder is None:
return None
return next((item for item in _items(folder) if item.get("code") == code), None)
def project_library_dir(project_root: str | Path) -> Path:
return Path(project_root) / "B08_Quantity" / LIBRARY_SUBDIR
def project_templates(project_root: str | Path | None) -> dict[str, dict[str, Any]]:
"""프로젝트에 박힌 양식 — 종류(type_id)별 하나. **표를 그릴 때 읽는 유일한 양식 자리.**"""
if not project_root:
return {}
return {
str(item["type_id"]): item
for item in _items(project_library_dir(project_root))
if item.get("type_id")
}
def import_item(project_root: str | Path, item: dict[str, Any], tier: str) -> None:
"""고른 항목을 프로젝트 작업본에 박음 — 같은 종류의 옛 것은 지움(종류당 하나).
⚠ 새 것을 먼저 쓰고 옛 것을 지움 — 쓰다 실패하면 옛 양식이 남아 조용히 기본으로 안 떨어짐.
"""
code = str(item.get("code") or "")
if not CODE_PATTERN.match(code):
raise ValueError(f"항목 코드 모양이 아님: {code!r}")
folder = project_library_dir(project_root)
folder.mkdir(parents=True, exist_ok=True)
body = {**item, "imported_from": tier}
target = folder / f"{code}.json"
target.write_text(json.dumps(body, ensure_ascii=False, indent=2), encoding="utf-8")
for path in folder.glob("*.json"):
if path == target:
continue
if json.loads(path.read_text(encoding="utf-8")).get("type_id") == item.get("type_id"):
path.unlink()
def pin_program_templates(project_root: str | Path) -> int:
"""[확정] 때 — 아직 안 박힌 종류는 **그 시점 프로그램 기본**을 박음. 박은 수.
확정은 정본을 굳히는 자리라, 뒤에 프로그램 기본을 고쳐도 확정한 값이 안 흔들려야 함
(브레인 판정). 확정 전(GET 만 한 프로젝트)은 기본을 그대로 읽음 — 정본이 아님.
"""
pinned = project_templates(project_root)
count = 0
for item in _items(TEMPLATE_DIR):
if item.get("type_id") and item.get("code") and item["type_id"] not in pinned:
import_item(project_root, item, "program")
count += 1
return count