feat(795-4): B09 사본 규칙 — 만든 때 그대로 · [단가 갱신] 값만 · [재생성] 따로 · 시점 섞임 고침
- 구조물 · 서고 사본 단가 묶음 = M02 일위대가 · SC · SK 로직 사본 줄 먼저(`collect(over)`) · M01 에서 지워진 키 = `사본_남은줄.json`
- 단가 책 새 줄([저장] · 첫 굳힘) = 연결 사본 `단가.json` 자원 줄(구조물 지정 때 값 · `Linked.frozen_rows`) · 없는 자원만 지금 M01 + 책 `now_keys` · 입력 `now` · 알림 한 줄
- POST …/estimation/prices 기본 = 단가 갱신(값만 · 사본 재적용 없음) · job 재생성 = 옛 [업데이트] · 미리 보기 단가 갱신 = 바뀐 자원 · 총괄 · 대가 합 전 → 후
- B09 화면 단추 [업데이트] → [단가 갱신] · 미리 보기 창 [재생성] → 재생성 미리 보기 → [적용] · 안내 글 둘 이름 맞춤
- 옮기기 도구(보기만) — 프로젝트 층 못셈 14 = 「못셈(옛 SP 참조)」 갈래 · B09 구조물 · 서고 사본 대조(그대로 · 재생성 금액)
- 새 시험 test_795_4_b09_copy(8) · 낡은 시험 고침: test_203_1 · test_296 = 단추 이름 「업데이트」 · 호버 글 · 몸 `{ user }` 를 박아 둔 것 → 795-4 이름 · job 덧칸으로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,11 @@
|
||||
SK = `calc_doc` 기준(노 · 재 · 경 · 계) · SC · 일위대가 = `Labor_Doc.calc` 기준합 · 반영합.
|
||||
같음 = 파일에 `사본` 칸만 더해 씀(그 밖 칸 그대로) · 다름 · 못 셈 = 안 씀(목록) ·
|
||||
닿을 것 없음(바탕 참조 SK · 참조 · 손 단가 줄만) = 그대로.
|
||||
프로젝트 층 일위대가 = 세기 · 대조만(B09 사본 규칙 795-4 와 함께 옮김).
|
||||
프로젝트 층 일위대가 = 세기 · 대조만(795-4 · 쓰지 않음) — 옛 SP 참조(프로젝트 층은 굳힌
|
||||
사본만 읽음 · 마스터 SP 를 바로 가리키는 옛 꼴) = 「못셈(옛 SP 참조)」 갈래.
|
||||
B09 사본(795-4 · 보기만) = 구조물 사본 · 서고 사본 — 만든 때 그대로 셈(옮길 것 없음 ·
|
||||
상태 「그대로」) ·
|
||||
`재생성` = 지금 원본으로 다시 가져오면 금액(바뀌면 `재생성다름`) · 못 셈 = 까닭.
|
||||
되돌릴 길 = 쓰기 전 파일 그대로 `--backup` 폴더에(층 자리 그대로) · `--restore` = 다시 덮음.
|
||||
|
||||
./venv/Scripts/python.exe -m M02_MasterTemplete.M02_Copy_Migrate # 보기만(표)
|
||||
@@ -100,6 +104,65 @@ def _backup_path(path: Path, backup: Path) -> Path:
|
||||
raise ValueError(f"층 밖 파일 「{path}」")
|
||||
|
||||
|
||||
OLD_SP = "위 층만 가리킴" # 프로젝트 층 문서가 마스터 SP 를 바로 가리킴(굳힌 사본 아님)
|
||||
LOCAL = ("입력값.json", "고른자재.json", "면적.json") # 재생성 대조 때 사본 값 그대로
|
||||
|
||||
|
||||
def _b09_files() -> list[tuple[str, str, Path]]:
|
||||
"""(어디, 종류, 폴더) — 프로젝트 구조물 사본 · B09 서고 사본."""
|
||||
root = layers.storage_root()
|
||||
out = []
|
||||
for kind, pattern in (
|
||||
("구조물사본", "*/*/*/templates/structures/*/manifest.json"),
|
||||
("서고사본", "*/*/*/B09_Estimation/v1/library/*/manifest.json"),
|
||||
):
|
||||
for p in sorted(root.glob(pattern)):
|
||||
at = p.parents[3] if kind == "구조물사본" else p.parents[4]
|
||||
out.append((f"B09 {at.name}", kind, p.parent))
|
||||
return out
|
||||
|
||||
|
||||
def _copy_money(files: dict[str, Any]) -> Any:
|
||||
from M02_MasterTemplete import M02_Item_Prices as prices
|
||||
|
||||
labor = files.get("일위대가.json")
|
||||
if not isinstance(labor, dict):
|
||||
return None
|
||||
got = prices.calc(
|
||||
labor,
|
||||
files.get("단가.json") or {},
|
||||
files.get("입력값.json") or {},
|
||||
files.get("고른자재.json") or {},
|
||||
files.get("면적.json"),
|
||||
)
|
||||
return _round({k: got.get(k) for k in ("기준합", "반영합")})
|
||||
|
||||
|
||||
def b09_check(kind: str, folder: Path) -> dict[str, Any]:
|
||||
"""B09 사본 하나(보기만) — {상태: 그대로 | 필요없음 | 못셈, 전, 재생성?, 재생성다름?, 까닭?}."""
|
||||
from B09_Estimation import B09_Estimation_Bill_Library as lib
|
||||
from M02_MasterTemplete.M02_Item_Source import build_source, read_json
|
||||
|
||||
own = {p.name: read_json(p) for p in folder.glob("*.json")}
|
||||
manifest = own.get("manifest.json") or {}
|
||||
try:
|
||||
before = _copy_money(own)
|
||||
except Exception as e: # noqa: BLE001 — 못 셈 = 목록
|
||||
return {"상태": "못셈", "까닭": str(e) or type(e).__name__}
|
||||
if before is None:
|
||||
return {"상태": "필요없음"}
|
||||
try:
|
||||
if kind == "서고사본":
|
||||
fresh, _ = lib._build(*lib._source_of(manifest))
|
||||
lib._apply(fresh, str(manifest.get("종류")), manifest.get("값") or {})
|
||||
else:
|
||||
fresh = build_source(manifest["원본"])
|
||||
after = _copy_money({**fresh, **{n: own[n] for n in LOCAL if n in own}})
|
||||
except Exception as e: # noqa: BLE001 — 원본 없음 · 못 셈 = 재생성 대조만 빠짐
|
||||
return {"상태": "그대로", "전": before, "까닭": f"재생성 대조 못 함 — {e}"}
|
||||
return {"상태": "그대로", "전": before, "재생성": after, "재생성다름": after != before}
|
||||
|
||||
|
||||
def migrate(apply: bool = False, backup: Path | None = None) -> list[dict[str, Any]]:
|
||||
"""모든 층 — 문서마다 {어디, 종류, 키, 상태, 씀, …} · `apply` = 같음만 씀(프로젝트 층 빼고)."""
|
||||
if apply and backup is None:
|
||||
@@ -107,6 +170,8 @@ def migrate(apply: bool = False, backup: Path | None = None) -> list[dict[str, A
|
||||
out = []
|
||||
for where_, kind, path, project in _files():
|
||||
got = check(kind, path)
|
||||
if project and got["상태"] == "못셈" and OLD_SP in got.get("까닭", ""):
|
||||
got["상태"] = "못셈(옛 SP 참조)"
|
||||
copy = got.pop("사본", None)
|
||||
row = {"어디": where_, "종류": kind, "키": path.stem, **got, "씀": False}
|
||||
if apply and copy and not project:
|
||||
@@ -116,6 +181,9 @@ def migrate(apply: bool = False, backup: Path | None = None) -> list[dict[str, A
|
||||
atomic_write_json(path, {**json.loads(path.read_bytes()), skc.KEY: copy})
|
||||
row["씀"] = True
|
||||
out.append(row)
|
||||
for where_, kind, folder in _b09_files(): # 795-4 B09 사본 = 보기만
|
||||
out.append({"어디": where_, "종류": kind, "키": folder.name, **b09_check(kind, folder)})
|
||||
out[-1]["씀"] = False
|
||||
return out
|
||||
|
||||
|
||||
@@ -149,6 +217,6 @@ if __name__ == "__main__": # 브레인 · 사용자 승인 뒤만 --apply
|
||||
at = Path(args[args.index("--backup") + 1]) if "--backup" in args else None
|
||||
rows = migrate("--apply" in args, at)
|
||||
for one in rows:
|
||||
if one["상태"] not in ("있음", "필요없음", "같음"):
|
||||
if one["상태"] not in ("있음", "필요없음", "같음") or one.get("재생성다름"):
|
||||
print(json.dumps(one, ensure_ascii=False, default=str))
|
||||
print(json.dumps(table(rows), ensure_ascii=False, indent=1))
|
||||
|
||||
@@ -225,12 +225,25 @@ class _Collector:
|
||||
self.pick(row, item["요소"], item.get("단위"))
|
||||
|
||||
|
||||
#: M01 에서 지워진 사본 줄(795-4) 자리 — `Master` 가 그룹 아닌 「줄」 로 읽음
|
||||
COPY_FILE = "사본_남은줄.json"
|
||||
|
||||
|
||||
def collect(
|
||||
logic_keys: list[str], materials: list[str] | None = None, extra: list[dict] | None = None
|
||||
logic_keys: list[str],
|
||||
materials: list[str] | None = None,
|
||||
extra: list[dict] | None = None,
|
||||
over: list[dict] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""담은 로직 + 자재(MT)의 닫힌 단가 묶음 — `{파일 이름: 파일(줄은 모은 것만 · 차례 그대로)}` ·
|
||||
`extra` = 마스터 밖 로직 줄(굳힌 단가산출 719-1) — 그 줄이 닿는 것만 모음(줄은 안 담음)."""
|
||||
`extra` = 마스터 밖 로직 줄(굳힌 단가산출 719-1) — 그 줄이 닿는 것만 모음(줄은 안 담음) ·
|
||||
`over` = M02 로직 사본 줄(795-4) — 같은 키는 M01 줄 대신 · M01 에 없는 키 = `COPY_FILE`."""
|
||||
files, whole = master()
|
||||
mine = {str(r.get("키")): r for r in over or []}
|
||||
if mine:
|
||||
from M02_MasterTemplete import M02_Sangeun_Copy as skc
|
||||
|
||||
whole = skc.Copy(whole, skc._rows({"로직": list(mine.values())}))
|
||||
collector = _Collector(whole)
|
||||
for key in logic_keys:
|
||||
collector.logic(key)
|
||||
@@ -239,11 +252,19 @@ def collect(
|
||||
for code in materials or []:
|
||||
collector.add(code)
|
||||
out: dict[str, Any] = {}
|
||||
seen: set[str] = set()
|
||||
for name, data in files.items():
|
||||
slot = m01.items_key(data)
|
||||
rows = [row for row in data.get(slot) or [] if str(row.get("키")) in collector.keys]
|
||||
rows = [
|
||||
mine.get(str(row.get("키")), row)
|
||||
for row in data.get(slot) or []
|
||||
if str(row.get("키")) in collector.keys
|
||||
]
|
||||
seen.update(str(row.get("키")) for row in rows)
|
||||
if rows:
|
||||
out[name] = {**{k: v for k, v in data.items() if k != slot}, slot: rows}
|
||||
if left := [r for k, r in mine.items() if k in collector.keys and k not in seen]:
|
||||
out[COPY_FILE] = {"그룹": "사본", "줄": left}
|
||||
if collector.missing:
|
||||
out["_빠진"] = sorted(set(collector.missing))
|
||||
return out
|
||||
|
||||
@@ -180,8 +180,11 @@ def price_bundle(labor: dict[str, Any] | None, root: Path) -> dict[str, Any]:
|
||||
materials += fixed.link_keys(doc) # 705-1 고정 줄이 잇는 노임 · 자재 · 기계
|
||||
materials += sk.materials(copies)
|
||||
wanted = logic_keys + [code for code in materials if code]
|
||||
over = copy_rows([doc, *copies.values()]) # M02 로직 사본(795-4) = 만든 때 줄 먼저
|
||||
bundle = (
|
||||
prices.to_json(prices.collect(logic_keys, materials, sk_rows)) if wanted or sk_rows else {}
|
||||
prices.to_json(prices.collect(logic_keys, materials, sk_rows, over))
|
||||
if wanted or sk_rows
|
||||
else {}
|
||||
)
|
||||
if sk_rows:
|
||||
bundle[sk.FILE] = {"그룹": "로직", "원문": "자체", "줄": sk_rows}
|
||||
@@ -190,3 +193,16 @@ def price_bundle(labor: dict[str, Any] | None, root: Path) -> dict[str, Any]:
|
||||
): # 개인 자재(148-3 · 사전조합 것도 175-2) — 원본 목록이 바뀌어도 사본 그대로
|
||||
bundle[own.BUNDLE] = mine
|
||||
return bundle
|
||||
|
||||
|
||||
def copy_rows(docs: list[Any]) -> list[dict[str, Any]]:
|
||||
"""문서 · 굳힌 참조사본(SK 는 `원본`)의 로직 사본 줄(795-4) — 키 겹침 = 앞 문서 것."""
|
||||
from M02_MasterTemplete import M02_Sangeun_Copy as skc
|
||||
|
||||
out: dict[str, dict] = {}
|
||||
for doc in docs:
|
||||
for one in (doc, doc.get("원본") if isinstance(doc, dict) else None):
|
||||
if skc.has(one):
|
||||
for row in [*one[skc.KEY]["로직"], *(one[skc.KEY].get("단가") or [])]:
|
||||
out.setdefault(str(row.get("키")), row)
|
||||
return list(out.values())
|
||||
|
||||
Reference in New Issue
Block a user