feat(M02): 옛 표 틀 새 판으로 갱신 — Migrate --refresh-tables · 손 값 · 변수 · 보기 지킴 · _initial 도 같은 판 (브레인 최종 확인)
- 계산 열에 바인딩이 붙은 옛 틀만 골라 시스템 새 판으로 - 손댄 것이 없으면 시스템 파일 그대로 복사(판이 시스템과 같음) · 있으면 고쳐 쓰고 manifest 에 출처 판 - 기존 프로젝트 6개 작업본 · _initial 갱신 완료 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LuapLYqN1GGFD8Y1PStD5
This commit is contained in:
@@ -209,6 +209,15 @@ def _write_manifest(layer_dir: str | Path, entries: dict[str, Any]) -> None:
|
||||
atomic_write_json(Path(layer_dir) / MANIFEST_NAME, {"양식": entries})
|
||||
|
||||
|
||||
def note_source(
|
||||
layer_dir: str | Path, kind: str, name: str, source_layer: str, version: str
|
||||
) -> None:
|
||||
"""manifest 한 줄 — 복사가 아니라 고쳐 쓴 양식의 출처 · 판."""
|
||||
manifest = read_manifest(layer_dir)
|
||||
manifest[f"{kind}/{name}"] = _stamp(source_layer, name, version)
|
||||
_write_manifest(layer_dir, manifest)
|
||||
|
||||
|
||||
def _stamp(layer: str, name: str, version: str | None, **extra: Any) -> dict[str, Any]:
|
||||
entry = {
|
||||
"층": layer,
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
프로젝트마다 `seed_project(only_missing=True)` — 작업본 · `_initial/` 에 **없는 것만 더함**.
|
||||
이미 있는 양식 · 다른 파일은 안 건드림 · 폴더가 없는 프로젝트는 건너뜀(만들지 않음).
|
||||
다시 돌려도 됨 — 시스템 양식이 늘었으면 그 몫만 더해짐.
|
||||
`--refresh-tables` — 옛 표 틀(계산 열에 바인딩)만 새 판으로 · 손 값은 지킴.
|
||||
|
||||
./venv/Scripts/python.exe M02_MasterTemplete/M02_Template_Migrate.py [--dry-run] [--report 파일]
|
||||
./venv/Scripts/python.exe M02_MasterTemplete/M02_Template_Migrate.py \\
|
||||
[--dry-run] [--refresh-tables] [--report 파일]
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import copy
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
@@ -21,6 +24,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from config import config_system # noqa: E402
|
||||
from config.config_db import close_db_pool, get_db_pool, init_db_pool # noqa: E402
|
||||
from M02_MasterTemplete import M02_Table_Fill as fill # noqa: E402
|
||||
from M02_MasterTemplete import M02_Template_Layers as layers # noqa: E402
|
||||
|
||||
|
||||
@@ -51,13 +55,62 @@ def project_root(storage_path: str | None) -> Path | None:
|
||||
return path
|
||||
|
||||
|
||||
def migrate(projects: list[dict[str, Any]], *, dry_run: bool = False) -> list[dict[str, Any]]:
|
||||
"""프로젝트마다 넣은 것 `[{…프로젝트, 작업본, 초기, 상태}]`."""
|
||||
def _stale_frame(document: Any) -> bool:
|
||||
"""옛 표 틀 — 계산 열에 바인딩(설계값 딱지)이 붙은 판."""
|
||||
return isinstance(document, dict) and any(
|
||||
isinstance(column, dict)
|
||||
and column.get("바인딩")
|
||||
and (column.get("식") or column["바인딩"].get("값") == "식")
|
||||
for column in document.get("열") or []
|
||||
)
|
||||
|
||||
|
||||
def refresh_tables(root: Path, *, dry_run: bool = False) -> list[str]:
|
||||
"""옛 표 틀을 시스템 새 판으로 — 작업본 · `_initial/` 둘 다 · 줄(손 값) · 변수 · 보기는 지킴.
|
||||
|
||||
손댄 것이 없으면 시스템 파일을 그대로 복사(판이 시스템과 같음).
|
||||
"""
|
||||
done: list[str] = []
|
||||
for row in layers.list_templates(layers.system_dir()):
|
||||
if row["종류"] != "table":
|
||||
continue
|
||||
system = layers.read_template(layers.system_dir(), "table", row["이름"])
|
||||
for folder in (layers.project_dir(root), layers.initial_dir(root)):
|
||||
got = layers.read_template(folder, "table", row["이름"])
|
||||
if not system or not got or not _stale_frame(got["문서"]):
|
||||
continue
|
||||
done.append(f"{folder.name}/table/{row['이름']}")
|
||||
if dry_run:
|
||||
continue
|
||||
old = fill.strip_design(got["문서"])
|
||||
doc = copy.deepcopy(system["문서"])
|
||||
doc["변수"] = {**(doc.get("변수") or {}), **(old.get("변수") or {})}
|
||||
doc.update({key: old[key] for key in ("줄", "보기") if key in old})
|
||||
if doc == system["문서"]:
|
||||
layers.copy_templates(
|
||||
layers.system_dir(),
|
||||
folder,
|
||||
source_layer="system",
|
||||
kind="table",
|
||||
name=row["이름"],
|
||||
)
|
||||
else:
|
||||
layers.write_template(folder, "table", row["이름"], doc)
|
||||
layers.note_source(folder, "table", row["이름"], "system", system["판"])
|
||||
return done
|
||||
|
||||
|
||||
def migrate(
|
||||
projects: list[dict[str, Any]], *, dry_run: bool = False, refresh: bool = False
|
||||
) -> list[dict[str, Any]]:
|
||||
"""프로젝트마다 넣은 것 `[{…프로젝트, 작업본, 초기, 표갱신, 상태}]`."""
|
||||
system = {f"{row['종류']}/{row['이름']}" for row in layers.list_templates(layers.system_dir())}
|
||||
report = []
|
||||
for project in projects:
|
||||
root = project_root(project.get("storage_path"))
|
||||
entry = {**project, "작업본": [], "초기": [], "상태": ""}
|
||||
entry = {**project, "작업본": [], "초기": [], "표갱신": [], "상태": ""}
|
||||
if root is not None and refresh:
|
||||
entry["표갱신"] = refresh_tables(root, dry_run=dry_run)
|
||||
if root is None:
|
||||
entry["상태"] = "폴더 없음 — 건너뜀"
|
||||
elif dry_run:
|
||||
@@ -85,14 +138,14 @@ def render(report: list[dict[str, Any]], system: list[dict[str, Any]]) -> str:
|
||||
"- 방법: 작업본 · `_initial/` 에 없는 것만 더함 · 있는 양식 · 다른 파일은 그대로",
|
||||
f"- 프로젝트 {len(report)}개 · 넣음 {sum(r['상태'] == '넣음' for r in report)}개",
|
||||
"",
|
||||
"| 회사 | 만든 사람 | 프로젝트 | ID | 넣은 양식 | 상태 |",
|
||||
"| --- | --- | --- | --- | --- | --- |",
|
||||
"| 회사 | 만든 사람 | 프로젝트 | ID | 넣은 양식 | 표 틀 새 판 | 상태 |",
|
||||
"| --- | --- | --- | --- | --- | --- | --- |",
|
||||
]
|
||||
for row in report:
|
||||
added = " · ".join(row["작업본"]) or "—"
|
||||
lines.append(
|
||||
f"| {row['company_id']} | {row.get('owner') or row['user_id']} | {row['name']} "
|
||||
f"| `{row['id']}` | {added} | {row['상태']} |"
|
||||
f"| `{row['id']}` | {added} | {' · '.join(row['표갱신']) or '—'} | {row['상태']} |"
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
@@ -100,6 +153,7 @@ def render(report: list[dict[str, Any]], system: list[dict[str, Any]]) -> str:
|
||||
async def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
parser.add_argument("--refresh-tables", action="store_true")
|
||||
parser.add_argument("--report", type=Path)
|
||||
args = parser.parse_args()
|
||||
await init_db_pool()
|
||||
@@ -107,7 +161,7 @@ async def main() -> None:
|
||||
projects = await _projects()
|
||||
finally:
|
||||
await close_db_pool()
|
||||
report = migrate(projects, dry_run=args.dry_run)
|
||||
report = migrate(projects, dry_run=args.dry_run, refresh=args.refresh_tables)
|
||||
text = render(report, layers.list_templates(layers.system_dir()))
|
||||
if args.report:
|
||||
args.report.write_text(text, encoding="utf-8")
|
||||
|
||||
@@ -295,3 +295,44 @@ def test_프로젝트_표_저장은_서버가_설계값을_걸러_씀(world: dic
|
||||
stored = layers.read_template(_root(world, P1) / "templates", "table", "구조물집계표")["문서"]
|
||||
assert stored["줄"][1] == {"id": "s40.00", "값": {"sta": "NO.2", "h_intake": 2}}
|
||||
assert "알림" not in stored and all("|" not in c["id"] for c in stored["열"])
|
||||
|
||||
|
||||
def test_옛_표_틀은_새_판으로_손_값은_지킴(world: dict[str, Any]) -> None:
|
||||
from M02_MasterTemplete import M02_Template_Migrate as migrate
|
||||
|
||||
new = {
|
||||
"판": 2,
|
||||
"열": [{"id": "a", "식": "1", "펼침틀": {"묶음": "g"}}],
|
||||
"줄": [],
|
||||
"변수": {"k": 1},
|
||||
}
|
||||
old = {
|
||||
"판": 1,
|
||||
"열": [{"id": "a", "식": "1", "바인딩": {"종류": "pipe", "값": "식"}}, {"id": "memo"}],
|
||||
"줄": [{"id": "s10.00", "값": {"sta": "NO.0+10", "memo": "손"}}],
|
||||
"변수": {"k": 5},
|
||||
}
|
||||
layers.write_template(layers.system_dir(), "table", "구조물집계표", new)
|
||||
root2, root1 = _root(world, P2, user=43), _root(world, P1)
|
||||
for folder in (root1 / "templates", root1 / "templates/_initial", root2 / "templates"):
|
||||
layers.write_template(folder, "table", "구조물집계표", old)
|
||||
layers.write_template(
|
||||
root2 / "templates", "table", "구조물집계표", {**old, "줄": [], "변수": {"k": 1}}
|
||||
)
|
||||
|
||||
assert migrate.refresh_tables(root1, dry_run=True) == [
|
||||
"templates/table/구조물집계표",
|
||||
"_initial/table/구조물집계표",
|
||||
]
|
||||
migrate.refresh_tables(root1)
|
||||
work = layers.read_template(root1 / "templates", "table", "구조물집계표")["문서"]
|
||||
assert work["열"] == new["열"] and work["변수"] == {"k": 5} # 틀만 새 판 · 변수 · 손 값 지킴
|
||||
assert work["줄"] == old["줄"]
|
||||
system_version = layers.version_of(layers.system_dir() / "table/구조물집계표.json")
|
||||
initial = root1 / "templates/_initial"
|
||||
assert layers.version_of(initial / "table/구조물집계표.json") != system_version # 변수 다름
|
||||
assert layers.read_manifest(root1 / "templates")["table/구조물집계표"]["판"] == system_version
|
||||
assert migrate.refresh_tables(root1) == [] # 다시 돌려도 그대로
|
||||
# 손댄 것 없는 작업본은 시스템 파일 그대로(판이 같음)
|
||||
migrate.refresh_tables(root2)
|
||||
assert layers.version_of(root2 / "templates/table/구조물집계표.json") == system_version
|
||||
|
||||
Reference in New Issue
Block a user