"""Create the app pier '옥성교P1' (tapered T on piles, plan skew 18 deg; scope G1 + G5) through the web client: new pier (Ulsan template as a starting copy), upload a copy of RAG/옥성교P1.dwg, then write params / expected from the reading (판독/g1_values.json; DOC/02_분석/분석_옥성교P1교각도면판독_2026-10-14_v0.1.00.md) and decisions O1-O5. usage: python make_pier.py (refuses if the pier already exists) """ import json import sys from io import BytesIO from pathlib import Path HERE = Path(__file__).resolve().parent ROOT = HERE.parents[1] APP = ROOT / "Code" / "교각모델링앱" sys.path.insert(0, str(APP / "web")) import app as webapp # noqa: E402 import store # noqa: E402 NAME = "옥성교P1" V = json.loads((HERE / "판독" / "g1_values.json").read_text(encoding="utf-8")) cf = V["coping_front"] steps = cf["top_steps"] prof = [(cf["bottom_flat_half"] * -1, cf["z_bottom"]), (cf["bottom_flat_half"], cf["z_bottom"]), (cf["tip_x"], cf["z_tip"])] for s in reversed(steps): prof += [(s["x1"], s["z"]), (s["x0"], s["z"])] prof += [(-cf["tip_x"], cf["z_tip"])] TABLE = {1: (21398, 173), 2: (21432, 173), 3: (21465, 173), 4: (21411, 173), 5: (21351, 173), 6: (21419, 194), 7: (21453, 194), 8: (21486, 194), 9: (21433, 194), 10: (21372, 194)} pads = {int(k): v for k, v in V["pads_outer"].items()} step_of = lambda no: steps[(no - 1) % 5]["z"] # NO.1-5 / 6-10 lie on step 1..5 (skewed bearing lines) YF, YR = pads[1]["centre"][1], pads[6]["centre"][1] pedestals = [] for i, pd in enumerate(V["pedestals"]): zs = step_of(i + 1) pedestals.append({"no": i + 1, "corners": pd["corners"], "z_bottom": zs, "top": {"y0": YF, "z0": zs + 173, "y1": YR, "z1": zs + 194}, "src": pd["src"] + "; 상면 앞 +173 → 뒤 +194 (O2 [추론])"}) bearings = [{"no": no, "type": "3000 kN", "row": "front" if no <= 5 else "rear", "centre": pads[no]["centre"], "rotation_deg": pads[no]["rotation_deg"], "seat_z": step_of(no), "el_x": TABLE[no][0], "h1": TABLE[no][1], "h2": 50, "A": 700, "B": 650, "pedestal": (no - 1) % 5} for no in range(1, 11)] params = { "_schema": "pier-T/1", "pier_id": "P1", "_meta": {"structure": "옥성교 P1 교각 (T형, 말뚝기초, 직선 경사 코핑, 평면 사각 18°)", "status": "형상(G1)·받침(G5) 판독 2026-10-14, 결정 O1~O5 사용자 확정. 철근 G2~G4 미판독(범위 밖)", "units": "mm; Z = EL x 1000", "frame": "X 코핑 길이 방향, Y 평면 위쪽, 원점 = 기둥 중심", "sources": {"D1": "RAG/옥성교P1.dwg (교각 일반도(1) + 배근도)"}, "reading": "DOC/02_분석/분석_옥성교P1교각도면판독_2026-10-14_v0.1.00.md, Code/옥성P1판독/판독/g1_values.json"}, "scope": ["G1", "G5"], "builders": {"G1": "concrete_tapered.py"}, "lean_concrete": {"size_x": 9800.0, "size_y": 9800.0, "z_bottom": 12075.0, "z_top": 12175.0, "src": V["lean"]["src"]}, "footing": {"size_x": 9600.0, "size_y": 9600.0, "z_bottom": 12175.0, "thickness": 2200.0, "src": V["footing"]["src"]}, "piles": {"count_x": 5, "count_y": 5, "pitch_x": 2000.0, "pitch_y": 2000.0, "diameter": 609.0, "length": 22000.0, "head_embedment": 100.0, "kind": "내부굴착강관말뚝 Ø609.0x12T (속찬 원기둥, O5)", "src": V["piles"]["src"] + "; 기초 평면 '5@1.600'은 오기 (O1)"}, "column": {"shape": "circular", "diameter": 2500.0, "height": 3800.0, "src": V["column"]["src"]}, "coping": {"form": "tapered", "profile_xz": [list(p) for p in prof], "width_y": 3300.0, "plan_length": 12000.0, "top_length": 12000.0, "z_tip": cf["z_tip"], "z_top_left": steps[0]["z"], "z_top_right": steps[-1]["z"], "plan_corner_radius": 0, "side_taper": {"half_width_top": 1650.0, "z_taper_top": 19675.0, "half_width_bottom": 1250.0}, "skew_deg": V["skew_deg"], "src": {"profile": cf["src"] + " (Y = 0 단면, 상면 5단)", "side": V["coping_side"]["src"], "skew": V["coping_plan"]["src"] + " (O3)"}}, "materials": {"fck": {"lean": "16MPa", "footing": "27MPa", "column": "35MPa", "coping": "35MPa", "seat": "35MPa"}, "src": "일반도 주기 '버림 CON'C fck = 16 MPa', '구체 fck = 27 MPa'(기초), '구체 fck = 35 MPa'; 대좌 35 [추론]"}, "bearing_seats": {"_note": "shared pedestals (one per bearing line, both rows), sloped top (O2); pads rotated with the skew", "pad_margin_bottom": 200, "pad_margin_top": 100, "pedestals": pedestals, "bearings": bearings, "src": "일반도 평면 대좌 1047F~104A2, 패드 10D96~10DB3; ELEVATION TABLE h1·h2·A·B"}, "sole_plates": {"table": [{"no": no, "S": 1.765, "L": 800, "W": 750, "T": 29, "T1": 22, "T2": 36} for no in range(1, 11)], "h3": 148, "bearing_size": "AB", "bearing_shrink": 100, "colours": {"bearing": [0, 0, 0], "sole_plate": [150, 150, 150]}, "sole_plate_src": "솔플레이트 상세 TABLE; S > 0 -> 두꺼운 쪽 +Y (T2 36, O4 [추론])", "src": "교량받침 A x B (700 x 650), h3 148 (ELEVATION TABLE); T = h4 29"}, "rebar_marks": {}, "rebar_marks_src": "철근 G2~G4 다음 Task", } expected = { "_schema": "pier-T-expected/1", "pier_id": "P1", "tol_mm": 1.0, "_note": "독립 기대값: 일반도 치수 문자·EL 문자·받침 표. [작도] = 치수 문자 없음(사각 18°, 대좌 외곽, 받침 평면 위치)", "checkers": {"G1": "g1_skew.py"}, "negative": {"G1": [{"id": "BP02", "d": [3, 0, 0]}, {"id": "BS03", "d": [0, 0, 2]}], "G5": [{"id": "BB04", "d": [5, 0, 0]}, {"id": "SP03", "d": [0, 0, 5]}]}, "planned_fail": [], "G1": {"footing": {"dx": 9600, "dy": 9600, "z0": 12175, "thickness": 2200, "src": "'9.600', '2.200', 'EL. 12.175'"}, "lean": {"dx": 9800, "dy": 9800, "thickness": 100, "src": "버림 '100', 9,800"}, "column": {"d": 2500, "height": 3800, "src": "'2.500', '3.800'"}, "piles": {"nx": 5, "ny": 5, "pitch": 2000, "edge": 800, "d": 609, "length": 22000, "head_embedment": 100, "src": "'4@2.000=8.000' (정면·단면), '800', 'Ø609.0x12T, L=22.0M', 머리 '100' (O1)"}, "coping": {"bottom_flat_half": 1350, "tip_half": 6000, "soffit_rise": 1500, "width_y": 3300, "side_bottom_width": 2500, "skew_deg": 18.0, "step_bounds": [-3773, -1297, 1179, 3655], "step_z": [21175, 21209, 21242, 21188, 21128], "src": "'12.000', '2.700'(1,350 x 2), '1.500' x 2, '3.300'(1,120+1,141+1,039), 기둥 폭 2,500; 상면 단 경계 '2.227, 3@2.476, 2.344'; " "단 높이 = EL.'X' - h1 - h2 (NO.1~5); 사각 18° [작도]"}, "seats": {"n_pedestals": 5, "table": {str(no): [TABLE[no][0], TABLE[no][1], 50, 700, 650] for no in range(1, 11)}, "pos": {str(no): pads[no]["centre"] for no in range(1, 11)}, "src": "ELEVATION TABLE; 받침 평면 위치 [작도] (단면 '1.120, 1.141, 1.039' = y -530 / 611 ✓)"}}, "G5": {"el_x": {str(no): TABLE[no][0] for no in range(1, 11)}, "S": {str(no): 1.765 for no in range(1, 11)}, "W": {str(no): 750 for no in range(1, 11)}, "L": 800, "T": 29, "h3": 148, "bearing_size": {str(no): [700, 650] for no in range(1, 11)}, "bearing_shrink": 100, "thick_end": "+Y", "colours": {"bearing": [0, 0, 0], "sole_plate": [150, 150, 150]}, "src": "ELEVATION TABLE (EL.'X', h3, A, B); 솔플레이트 상세 TABLE (S, L, W, T1 22 / T2 36 -> +Y, O4)"}, } if __name__ == "__main__": if (store.DATA / NAME).exists(): sys.exit(f"pier exists: {NAME}") c = webapp.app.test_client() assert c.post("/pier/new", data={"name": NAME, "type": "T형", "template": "tpl:산외교울산P1형"}).status_code == 302 dwg = ROOT / "RAG" / "옥성교P1.dwg" r = c.post(f"/pier/{NAME}/upload", data={"dwg": [(BytesIO(dwg.read_bytes()), dwg.name)]}, content_type="multipart/form-data") assert r.status_code == 302, r.status_code p = store.Pier(NAME) params["display"] = p.load("params")["display"] expected["rebar_palette"] = p.load("expected")["rebar_palette"] p.save("params", params) p.save("expected", expected) meta = store.rjson(p.dir / "pier.json") meta["template"] = "신규 판독 (옥성교 P1 판독서 v0.1.00)" store.wjson(p.dir / "pier.json", meta) print("created", p.dir, "texts", len(p.texts()), "profile", prof)