chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합

- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환
- Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용
- pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관)
- worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md)
- D:\Rhino-MCP 경로 참조를 새 위치로 치환

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ahnyongjooandClaude Sonnet 5.5 committed 2026-10-28 11:20:18 +09:00
commit 964a841de1
3978 files changed
+58013356

No files matched your search

@@ -0,0 +1,103 @@
"""Stage A test of the 입력 묶음 확인·확정 개편 (2026-10-16), no Rhino: variability classes, template-origin marking, F-field edit rule (D3),
V3 edit block. Own data root 검증/variability_test_data (recreated); real piers are not touched.
Checks: every bundle of the 6 protected templates has a class (100 %), grades follow 기획서 부록 A, a new pier from a template shows all
required bundles as "템플릿 원본 그대로" and an edited bundle drops out, an old pier without origin record claims nothing, F edits need a
reason and are logged, V3 edits are refused, saved templates get a variability.json."""
import json
import os
import re
import shutil
import sys
from pathlib import Path
APP = Path(__file__).resolve().parents[1]
TD = APP / "검증" / "variability_test_data"
if TD.exists():
shutil.rmtree(TD)
shutil.copytree(APP / "data" / "_템플릿", TD / "_템플릿", ignore=shutil.ignore_patterns("_이전"))
shutil.copytree(APP / "data" / "옥성교P1", TD / "옥성교P1", ignore=shutil.ignore_patterns("runs", "backup", "drawings"))
os.environ["PIERAPP_DATA"] = str(TD)
sys.path.insert(0, str(APP / "web"))
sys.path.insert(0, str(APP))
import app as webapp # noqa: E402
sys.path.insert(0, str(Path(__file__).resolve().parent))
from testlib import confirm_required # noqa: E402
import store # noqa: E402
from engine import variability # noqa: E402
ok = True
def check(name, cond, detail=""):
global ok
ok &= bool(cond)
print("PASS" if cond else "FAIL", name, str(detail)[:300], flush=True)
# ---- 1. classes of the protected templates
for t in store.templates():
d = TD / "_템플릿" / t["name"]
P, E = (json.loads((d / f).read_text(encoding="utf-8")) for f in ("params.json", "expected.json"))
v = json.loads((d / "variability.json").read_text(encoding="utf-8"))["sections"]
want = {f"params:{k}" for k in P} | {f"expected:{k}" for k in E}
check(f"{t['name']}: every bundle classified ({len(want)})", set(v) == want and all(s["grade"] in variability.GRADES for s in v.values()),
sorted(want ^ set(v))[:5])
v3 = sorted(k for k, s in v.items() if s["grade"] == "V3")
exp_v3 = {"운곡교P1형": [], "산외교울산P1형": ["params:coping_rebar"], "산외교울산P2형": ["params:coping_rebar"], "Ramp-A교P1형": ["params:coping_rebar"],
"옥성교P1형": ["params:coping_rebar", "params:footing_rebar"], "새재1교P1형": ["params:coping_rebar", "params:footing_rebar"]}[t["name"]]
check(f"{t['name']}: V3 = {exp_v3} (explicit bar lists, 부록 A)", v3 == exp_v3, v3)
check(f"{t['name']}: run settings / pier_id are F", all(v[k]["grade"] == "F" for k in ("params:pier_id", "expected:pier_id", "expected:checkers") if k in v))
# ---- 2. new pier from a template: all required bundles are "원본 그대로"
c = webapp.app.test_client()
r = c.post("/pier/new", data={"name": "원본시험", "type": "T형", "template": "tpl:새재1교P1형"})
check("new pier created", r.status_code == 302, r.status_code)
p = store.Pier("원본시험")
req = webapp.required(p)
same = {(s["file"], s["key"]) for s in p.sections() if p.section_meta(s["file"], s["key"])["origin"]}
check(f"all {len(req)} required bundles are still the template's original", req <= same and len(req) > 20, f"{len(req & same)}/{len(req)}")
html = c.get("/pier/원본시험").get_data(as_text=True)
check("pier page warns about original values and shows grade / source columns", f"템플릿 원본 그대로인 필수 묶음 {len(req)}개" in html
and "변수 등급" in html and "값 출처" in html and "V1 값 변수" in html)
check("pier folder holds its own variability.json and the origin record", (TD / "원본시험" / "variability.json").exists() and p.info.get("origin", {}).get("sha"))
# ---- 3. editing a V1 bundle drops it from the original list
col = json.loads(json.dumps(p.load("params")["column"]))
col["height"] = col["height"] + 100
r = c.post("/pier/원본시험/section/params/column", data={"value": json.dumps(col)})
check("V1 edit saved without reason", r.status_code == 302 and p.load("params")["column"]["height"] == col["height"])
check("edited bundle is no longer 'original'", p.section_meta("params", "column")["origin"] is False and p.section_meta("params", "footing")["origin"] is True)
# ---- 4. F fields need a reason (D3), the reason is logged
col2 = dict(col, shape="rectangular")
r = c.post("/pier/원본시험/section/params/column", data={"value": json.dumps(col2)})
check("F-field change without reason refused", "고정(F) 항목" in r.get_data(as_text=True) and p.load("params")["column"]["shape"] == "circular")
r = c.post("/pier/원본시험/section/params/column", data={"value": json.dumps(col2), "reason": "사각 기둥 시험"})
log = store.rjson(TD / "원본시험" / "edit_log.json", [])
check("F-field change with reason saved and logged", p.load("params")["column"]["shape"] == "rectangular" and log and log[-1]["reason"] == "사각 기둥 시험"
and log[-1]["fixed_fields"] == ["shape"], log[-1:] )
sc = p.load("params")["scope"]
r = c.post("/pier/원본시험/section/params/scope", data={"value": json.dumps(sc[:-1])})
check("F bundle (scope) change without reason refused", "고정(F) 항목" in r.get_data(as_text=True) and p.load("params")["scope"] == sc)
# ---- 5. V3 bundles cannot be edited by hand
before = p.load("params")["coping_rebar"]
r = c.post("/pier/원본시험/section/params/coping_rebar", data={"value": json.dumps(before)})
check("V3 bundle edit refused", "생성 변수(V3)" in r.get_data(as_text=True) and p.load("params")["coping_rebar"] == before)
check("V3 section page explains", "판독 스크립트를 다시 실행" in c.get("/pier/원본시험/section/params/coping_rebar").get_data(as_text=True))
# ---- 6. an old pier (no origin record) claims nothing
q = store.Pier("옥성교P1")
check("old pier: origin unknown (None), no 'original' claim", q.info.get("origin") is None and all(q.section_meta(s["file"], s["key"])["origin"] is None for s in q.sections()))
html = c.get("/pier/옥성교P1").get_data(as_text=True)
check("old pier page says the origin is not recorded", "템플릿 복사 기록이 없어" in html and "▲ 템플릿 원본 그대로" not in html)
# ---- 7. saving a template writes variability.json
store.save_template("원본시험", "시험형", "시험")
check("save_template writes variability.json", (TD / "_템플릿" / "시험형" / "variability.json").exists())
# ---- 8. confirmation still works and the confirm records are untouched by the new metadata
confirm_required(c, webapp, store, "원본시험", "시험자")
check("confirming every required bundle one by one works", not webapp.unconfirmed(store.Pier("원본시험")))
print("RESULT", "PASS" if ok else "FAIL")
sys.exit(0 if ok else 1)