- 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>
125 lines
6.1 KiB
Python
125 lines
6.1 KiB
Python
"""Test of improvements #34-2 (params <-> expected consistency, C9), #34-3 (field-level 'still the creation value', C3b) and #34-6 (point-list rule),
|
|
2026-10-20. No Rhino, no writes under data/ (reads only). Real piers: the finished 운곡교 P1 / P3 / P4 must show no mismatch; the backups of P3 / P4 taken
|
|
during the first real-use runs (data/<pier>/backup/before_*) hold the exact stale states that cost two gate runs each -- they must be caught.
|
|
Skipped parts (data missing) are reported, not failed."""
|
|
import copy
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
APP = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(APP))
|
|
sys.path.insert(0, str(APP / "tools"))
|
|
from testlib import verified_pier # noqa: E402
|
|
from engine import consistency, verify # noqa: E402
|
|
|
|
D = APP / "data"
|
|
ok = True
|
|
|
|
|
|
def check(name, cond, detail=""):
|
|
global ok
|
|
ok &= bool(cond)
|
|
print("PASS" if cond else "FAIL", name, str(detail)[:300], flush=True)
|
|
|
|
|
|
def load(d):
|
|
return json.loads((d / "params.json").read_text(encoding="utf-8")), json.loads((d / "expected.json").read_text(encoding="utf-8"))
|
|
|
|
|
|
def origin(d):
|
|
f = d / "origin_values.json"
|
|
return json.loads(f.read_text(encoding="utf-8")) if f.exists() else None
|
|
|
|
|
|
def run(P, E, org=None):
|
|
return verify.verify(P, E, [], {}, origin_values=org)
|
|
|
|
|
|
def c_of(res, b, cid):
|
|
return next(c for c in res[b]["checks"] if c["id"] == cid)
|
|
|
|
|
|
def bad_c9(res):
|
|
return sorted(b for b, r in res.items() if any(c["id"] == "C9" and c["st"] == "b" for c in r["checks"]))
|
|
|
|
|
|
# ---- 1. finished piers: no false mismatch
|
|
for n in ("운곡교P1", "운곡교P3", "운곡교P4"):
|
|
d = D / n if n == "운곡교P1" else verified_pier(n) # P3 / P4: the gate-verified copy (a live pier may be half-updated while it is being made again)
|
|
if d is None or not d.exists():
|
|
print("SKIP", n)
|
|
continue
|
|
P, E = load(d)
|
|
r = consistency.check(P, E)
|
|
check(f"{n}: {len(r)} linked values, none differ", r and all(x["ok"] for x in r), [x["id"] for x in r if not x["ok"]][:5])
|
|
res = run(P, E, origin(d))
|
|
check(f"{n}: C9 never b / C3b never w on the finished state", not bad_c9(res) and not any(c["id"] == "C3b" and c["st"] == "w" for r_ in res.values() for c in r_["checks"]),
|
|
bad_c9(res))
|
|
|
|
# ---- 2. the stale states of the first real-use runs are caught
|
|
p3 = D / "운곡교P3"
|
|
if p3.exists() and list((p3 / "backup").glob("before_hooks_up_*")):
|
|
bk = sorted((p3 / "backup").glob("before_hooks_up_*"))[0]
|
|
P, E = load(bk)
|
|
r = [x for x in consistency.check(P, E) if not x["ok"]]
|
|
check("P3 before_hooks_up: the 4 hooks_up points that no longer exist in c14.points are found (#34-6)",
|
|
sorted(x["id"] for x in r) == [f"coping_rebar.c14.hooks_up.{i}" for i in range(4)], [x["id"] for x in r])
|
|
res = run(P, E, origin(p3))
|
|
check("P3 before_hooks_up: params:coping_rebar becomes 불일치 (C9)", c_of(res, "params:coping_rebar", "C9")["st"] == "b" and res["params:coping_rebar"]["st"] == "b")
|
|
c3b = c_of(res, "params:coping_rebar", "C3b")
|
|
check("P3 before_hooks_up: C3b names the hooks_up coordinates that were never changed (#34-3)", c3b["st"] == "w" and "hooks_up" in c3b["text"], c3b["text"])
|
|
else:
|
|
print("SKIP P3 before_hooks_up")
|
|
if p3.exists() and list((p3 / "backup").glob("before_stage2_*")):
|
|
bk = sorted((p3 / "backup").glob("before_stage2_*"))[0]
|
|
P, E = load(bk)
|
|
r = [x for x in consistency.check(P, E) if not x["ok"]]
|
|
ids = {x["id"] for x in r}
|
|
check("P3 before_stage2: >= 40 mismatches", len(r) >= 40, len(r))
|
|
for must in ("piles.pitch_y", "coping.step2.z", "coping.step2.x0", "bearing.pitch", "column_rebar.hoop.count", "footing_rebar.bottom_X2.count", "footing_rebar.b9.y"):
|
|
check(f"P3 before_stage2: rule {must} caught", must in ids)
|
|
res = run(P, E, origin(p3))
|
|
check("P3 before_stage2: every gate bundle of expected is 불일치 before any gate runs", all(res[f"expected:G{i}"]["st"] == "b" for i in (1, 2, 3, 4)), bad_c9(res))
|
|
else:
|
|
print("SKIP P3 before_stage2")
|
|
p4 = verified_pier("운곡교P4") or D / "운곡교P4"
|
|
if p4.exists() and list((p4 / "backup").glob("before_table_apply_*")):
|
|
bk = sorted((p4 / "backup").glob("before_table_apply_*"))[0]
|
|
P, E = load(bk)
|
|
r = [x for x in consistency.check(P, E) if not x["ok"]]
|
|
check("P4 before_table_apply: the schedule copies that differ between the files are caught", any(x["id"].startswith("schedule.") for x in r), len(r))
|
|
else:
|
|
print("SKIP P4 before_table_apply")
|
|
|
|
# ---- 3. synthetic edits on a finished pier
|
|
if p3.exists():
|
|
P, E = load(p3)
|
|
E2 = copy.deepcopy(E)
|
|
E2["G1"]["piles"]["pitch_y"] = 1550
|
|
res = run(P, E2, origin(p3))
|
|
check("pile pitch changed in expected only -> expected:G1 and params:piles 불일치", res["expected:G1"]["st"] == "b" and res["params:piles"]["st"] == "b", bad_c9(res))
|
|
P2 = copy.deepcopy(P)
|
|
h = P2["coping_rebar"]["c14"]["hooks_up"][0]
|
|
P2["coping_rebar"]["c14"]["points"] = [[x, z + 5 if [x, z] == h or (x, z) == tuple(h) else z] for x, z in P2["coping_rebar"]["c14"]["points"]]
|
|
r = [x for x in consistency.check(P2, E) if not x["ok"]]
|
|
check("moving the point a hooks_up entry names breaks that entry (#34-6)", [x["id"] for x in r] == ["coping_rebar.c14.hooks_up.0"], [x["id"] for x in r])
|
|
P3_ = copy.deepcopy(P)
|
|
del P3_["coping_rebar"]["c14"]["hooks_up"]
|
|
check("no hooks_up -> no point-list rule, nothing else breaks", not [x for x in consistency.check(P3_, E) if not x["ok"]])
|
|
res = run(P, E, None)
|
|
check("without origin_values.json C3b is 해당 없음 (older piers)", c_of(res, "params:coping_rebar", "C3b")["st"] == "x")
|
|
|
|
# ---- 4. other pier types are not touched
|
|
for n in ("새재1교P1", "옥성교P1", "산외교울산P2", "Ramp-A교P1"):
|
|
d = D / n
|
|
if not d.exists():
|
|
print("SKIP", n)
|
|
continue
|
|
P, E = load(d)
|
|
r = consistency.check(P, E)
|
|
check(f"{n}: no false C9 mismatch ({len(r)} linked values)", all(x["ok"] for x in r), [x["id"] for x in r if not x["ok"]][:5])
|
|
|
|
print("RESULT", "PASS" if ok else "FAIL")
|
|
sys.exit(0 if ok else 1)
|