"""Ramp-A P1 form test (2026-10-13), no Rhino: table reading fixes and the pier inputs. - ELEVATION TABLE header below detail labels, window cut at the 솔플레이트 TABLE title, '1,000' integers - reference piers unchanged: table records / proposals equal 검증/open19_snapshot_after.json - Ramp-A table candidates all equal the hand-typed expected / params (independent transcription check) - gate reports of data/Ramp-A교P1 (engine run) PASS with negatives detected """ 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")) sys.path.insert(0, str(APP / "web")) from engine import reader # noqa: E402 import store # noqa: E402 from open19_snapshot import snapshot # 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) ref = json.loads((APP / "검증" / "open19_snapshot_after.json").read_text(encoding="utf-8")) now = snapshot() for k in ref: check(f"{k}: table records unchanged", ref[k]["records"] == now[k]["records"]) check(f"{k}: proposals unchanged", ref[k]["proposals"] == now[k]["proposals"]) p = store.Pier("Ramp-A교P1") tables = reader.table_candidates(p.texts()) el = [t for t in tables if t["key"] == "elevation"] recs = [r for _, r in reader.table_records(el[0]["rows"], lambda s: s.isdigit())] if el else [] check("ELEVATION TABLE: 2 rows NO.1 / NO.2", [r["_key"] for r in recs] == ["1", "2"], [r["_key"] for r in recs]) check("ELEVATION TABLE: all 15 columns read", recs and all(len(r) == 16 for r in recs), [len(r) for r in recs]) check("ELEVATION TABLE NO.2 EL.'X' 60.427, C3 1400", recs and recs[1].get("EL.'X'") == "60.427" and recs[1].get("C3") == "1400") sp = [t for t in tables if t["key"] == "sole_plate"] srec = [r for _, r in reader.table_records(sp[0]["rows"], lambda s: s.isdigit())] if sp else [] check("솔플레이트 TABLE: 2 rows, L '1,000'", [r["_key"] for r in srec] == ["1", "2"] and srec[0].get("L") == "1,000", srec) check("thousands separator", reader._int("1,000") == 1000 and reader._int("50") == 50) rows = reader.proposals(reader.field_candidates(tables), p.load("params"), p.load("expected")) diff = [(r["file"], r["path"], r["current"], r["candidate"]) for r in rows if not r["same"]] kinds = {r["file"] + "." + ".".join(str(x) for x in r["path"][:2]) for r in rows} check(f"candidates all equal the typed inputs ({len(rows)} rows)", rows and not diff, diff) check("candidates cover G1 seats, G5 el_x, params bearings and sole plates", {"expected.G1.seats", "expected.G5.el_x", "params.bearing_seats.bearings", "params.sole_plates.table"} <= kinds, sorted(kinds)) for g, n in (("G1", 97), ("G2", 26), ("G3", 48), ("G4", 126), ("G5", 39)): # G2, G3 2026-10-13; G4 coping rebar 2026-10-14 r = json.loads((p.dir / "runs" / "current" / f"{g}_gate.json").read_text(encoding="utf-8")) check(f"{g} gate PASS {n}/{n}, negatives detected, reload ok", r["verdict"] == "PASS" and r["summary"]["passed"] == n == r["summary"]["total"] and all(v["detected"] for v in r["negative"].values()) and (r["reload"]["ok"] or r.get("mode") == "batch"), r["summary"]) # batch mode (app 일괄 승인) skips save + reload on purpose (OPEN #25) r2 = json.loads((p.dir / "runs" / "current" / "G2.json").read_text(encoding="utf-8")) cov = r2["summary"]["min_cover"] check("G2 min side cover P1 107.5 / P2 91 / P3 76 (R 1,000 - radial - bar radius)", cov == {"P1": 107.5, "P2": 91.0, "P3": 76.0}, cov) check("scope G1~G5", p.scope() == ["G1", "G2", "G3", "G4", "G5"], p.scope()) r4 = json.loads((p.dir / "runs" / "current" / "G4.json").read_text(encoding="utf-8")) cov4 = r4["summary"]["cover"] check("G4 min cover of coping bars >= 40 (corner clipping)", all(v >= 40 for k, v in cov4.items() if not k.startswith("S")), cov4) check("G4 449 bars", sum(len([b for b in p.load("params")["coping_rebar"]["bars"] if b["mark"] == m]) for m in {b["mark"] for b in p.load("params")["coping_rebar"]["bars"]}) == 449) r3 = json.loads((p.dir / "runs" / "current" / "G3.json").read_text(encoding="utf-8")) cov3 = r3["summary"]["min_cover"] check("G3 min cover (side / bottom) as hand-computed", cov3 == {"P1-1": 158.0, "P1-2": 164.5, "P2-1": 142.0, "P3-1": 98.5, "P3-2": 92.0}, cov3) check("template Ramp-A교P1형 = pier inputs", all((store.DATA / "_템플릿" / "Ramp-A교P1형" / f).read_bytes() == (p.dir / f).read_bytes() for f in ("params.json", "expected.json"))) print("RESULT", "PASS" if ok else "FAIL") sys.exit(0 if ok else 1)