feat(733-2): 사람 길 만들 때 같은 내용 409 물음 · 복사 딸림 재사용

- 새 M02_Library_Ask — 같은 층 같은 내용(733-1 지문) = 409 {같은것: [{키, 이름, 층, 주인}], 까닭} · 몸 새로 = 그래도 새로 · 씀 = 그 키(새 문서 0 · 같은 내용 아닌 키 = 다시 409)
- 길 = 새로 만들기(빈 문서 · 같은 층 복사 = 안 물음) · [단가산출로 저장] from-line · B09 내 자료집(같은이름 409 와 한 몸 · 하위 호표 지문 = 굳힌 문서) · 공유 요청 승인(일위대가 · 씀 = 사본 키) · 실무 호표 [구조물 조합으로](구조물 조합 지문 = 줄 수량 넣음)
- 복사 딸림 carry — 받는 층에 같은 원본 + 같은 내용 사본이 있으면 그 키 재사용(refs.copied_from) · B09 하위 호표 재사용 목록 = 실무자료 한정 → 모든 사본 원본
- 낡은 시험 고침: test_703 자료집 같은 이름 물음 — 새 이름이라도 같은 내용이면 409(정함 3장 3) → new=True 로 새로 · 씀 확인 더함
- 새 시험 test_733_2_library_ask(새로 · from-line · 승인 · carry) · test_705_2 구조물 조합으로 409

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-02 13:54:28 +09:00
co-authored by Claude Opus 5.5
parent 8588743906
commit 40980910fa
12 changed files with 379 additions and 33 deletions
+123
View File
@@ -0,0 +1,123 @@
"""사람 길 만들 때 물음(PLAN 733-2 · 요구서_572 3장 3) — 같은 층 같은 내용 문서가 있으면 409.
409 detail = `{같은것: [{키, 이름, 층, 주인}], 까닭}` → 다시 보냄 몸 `새로: true` = 그래도 새로 ·
`씀: <키>` = 그것을 씀(새로 안 만듦 · 그 키를 돌려줌 · 같은 내용이 아니면 다시 409).
지문 = `M02_Library_Same`(일위대가 SC · 단가산출 SK) · 구조물 조합(labor) = 같은 꼴 + 줄 수량 넣음.
빈 문서(지문 없음) = 안 물음 · 같은 층 [복사] · 붙여넣기 = 안 물음(복사가 뜻).
부르는 쪽 = 지금 층(`store.at` 안).
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from common_util.common_util_derived_store import derived_key
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_Library_Same as same
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_MasterTemplete_Tree as tree
WHY = "같은 층에 같은 내용 문서가 있음 — 그것을 씀 · 그래도 새로"
def _cleaned(kind: str, doc: dict[str, Any]) -> dict[str, Any]:
"""저장될 꼴로 견줌(서버 정리) — 틀린 문서는 그대로(만들 때 까닭이 남)."""
try:
return refs.clean_doc(kind, {**doc, "키": refs.next_key(kind)})
except store.StoreError:
return doc
def _refuse(rows: list[dict[str, Any]], why: str = WHY) -> store.StoreError:
return store.StoreError(409, {"같은것": rows, "까닭": why})
def _pick(found: list[str], rows: list[dict[str, Any]], new: bool, use: str | None) -> str | None:
if use:
if use in found:
return use
raise _refuse(rows, f"고른 「{use}」 가 같은 내용이 아님 — 다시 고름")
if found and not new:
raise _refuse(rows)
return None
def ask(
kind: str,
doc: dict[str, Any],
new: bool = False,
use: str | None = None,
s: same.Same | None = None,
) -> str | None:
"""서고 문서(SC · SK) — 씀 키 | None(새로 만듦) · 같은 것 있고 물음 없으면 409 ·
`s` = 미리 채운 지문 한 벌(층 밖 참조 지문 등)."""
folder = store.root()
found = (s or same.Same()).find_same(folder, kind, _cleaned(kind, doc))
at = refs.where(folder)
rows = [
{
"키": key,
"이름": (refs.read_ref({"층": at[0], "주인": at[1], "키": key}) or {}).get("이름"),
}
for key in found
]
return _pick(found, [{**r, "층": at[0], "주인": at[1]} for r in rows], new, use)
def labor_finger(doc: Any) -> str | None:
"""구조물 조합 일위대가 지문 — 담은로직 · 자재 · 단위 · 규격(줄 수량 넣음 · 차례 무시)."""
if not isinstance(doc, dict):
return None
s = same.Same()
mats = [same.norm(m) for m in doc.get("자재") or []]
body = {
"단위": same.unit(doc.get("단위")),
"담은로직": same._sorted([s._line(x, (), True) for x in doc.get("담은로직") or []]),
"자재": same._sorted(mats),
"규격": same.norm(doc.get("규격")),
}
body = {k: v for k, v in body.items() if v not in same.EMPTY}
if not body.get("담은로직") and not body.get("자재"):
return None
return f"SU:{derived_key(body)}"
def ask_item(
folder: Path, doc: dict[str, Any], new: bool = False, use: str | None = None
) -> dict[str, Any] | None:
"""구조물 조합 항목(실무 호표 [구조물 조합으로]) — 씀 = `{key, 열, 이름}` | None(새로) · 409."""
from M02_MasterTemplete import M02_Labor_Doc as labor
try:
doc = {**labor.clean(doc), "규격": doc.get("규격")} # 저장될 꼴로 견줌
except store.StoreError:
pass
want = labor_finger(doc)
if not want:
return None
at = refs.where(folder)
with store.at(folder):
table = store.read("table", store.STRUCTURE_TABLE)["문서"]
cols = {c["id"]: c for c in table.get("열") or [] if tree.is_item(c)}
found, rows = [], []
for key, col in cols.items():
try:
have = json.loads((Path(folder) / "labor" / f"{key}.json").read_bytes())
except (OSError, ValueError):
continue
if labor_finger(have) == want:
name = " / ".join(tree.path_of(col))
found.append(key)
rows.append({"키": key, "이름": name, "층": at[0], "주인": at[1]})
got = _pick(found, rows, new, use)
if got is None:
return None
return {"key": got, "열": got, "이름": rows[found.index(got)]["이름"], "씀": True}
def create(kind: str, doc: dict[str, Any], new: bool = False, use: str | None = None) -> dict:
"""[새로 만들기](733-2) — 같은 것 물음 뒤 `refs.create` · 씀 = `{이름: 그 키, 씀: true}`."""
got = ask(kind, doc, new, use)
return {"이름": got, "씀": True} if got else refs.create(kind, doc)