"""Patch (2026-10-13, Ulsan P1): per-pier gate scope in the web app and the report. Gates outside params["scope"] are not listed / run / required; the previous gate of a gate is the previous gate in scope; optional sections (params.piles for a pier without piles) are not required for confirmation.""" from pathlib import Path APP = Path(__file__).resolve().parents[2] def patch(rel, reps): p = APP / rel s = p.read_text(encoding="utf-8") for a, b in reps: assert s.count(a) == 1, (rel, a[:70]) s = s.replace(a, b) p.write_text(s, encoding="utf-8") patch("web/store.py", [ (""" def gate_requirements(self, gate): need = COMMON + GATE_SECTIONS[gate] return [dict(file=f, key=k, **self.section_state(f, k)) for f, k in need]""", """ def gate_requirements(self, gate): need = COMMON + GATE_SECTIONS[gate] P = self.load("params") need = [(f, k) for f, k in need if not ((f, k) in OPTIONAL and k not in P)] return [dict(file=f, key=k, **self.section_state(f, k)) for f, k in need] def scope(self): \"\"\"Gates modelled for this pier (params["scope"], default all five).\"\"\" sc = self.load("params").get("scope") return [g for g in ("G1", "G2", "G3", "G4", "G5") if not sc or g in sc]"""), ("""GATE_SECTIONS = {""", """OPTIONAL = {("params", "piles")} # a pier on a spread footing has no piles (Ulsan P1) GATE_SECTIONS = {"""), ]) patch("web/app.py", [ (""" gates = [dict(id=g, name=GATE_NAMES[g], **p.gate_status(g)) for g in gate_ids()]""", """ gates = [dict(id=g, name=GATE_NAMES[g], **p.gate_status(g)) for g in p.scope()]"""), (""" out = [] for g in gate_ids(): for r in p.gate_requirements(g):""", """ out = [] for g in p.scope(): for r in p.gate_requirements(g):"""), (""" k = gate_ids().index(gate) if k > 0 and not p.gate_status(gate_ids()[k - 1])["approved"]: out.append(f"{gate_ids()[k - 1]} 미승인")""", """ ids = p.scope() if gate not in ids: return [f"{gate}는 이 교각의 범위 밖"] k = ids.index(gate) if k > 0 and not p.gate_status(ids[k - 1])["approved"]: out.append(f"{ids[k - 1]} 미승인")"""), (""" todo = [g for g in gate_ids() if not p.gate_status(g)["approved"]] if not todo:""", """ todo = [g for g in p.scope() if not p.gate_status(g)["approved"]] if not todo:"""), (""" for g in gate_ids()] for r in rows:""", """ for g in p.scope()] for r in rows:"""), (""" todo=[g for g in gate_ids() if not p.gate_status(g)["approved"]])""", """ todo=[g for g in p.scope() if not p.gate_status(g)["approved"]], out_of_scope=[g for g in gate_ids() if g not in p.scope()])"""), (""" if not any(p.gate_status(g)["report"] for g in gate_ids()):""", """ if not any(p.gate_status(g)["report"] for g in p.scope()):"""), (""" stale = {g for g in gate_ids() if p.gate_status(g)["stale"]} out = report_files.write(p.dir, p.run_dir, request.form.get("by", "").strip(), stale)""", """ stale = {g for g in p.scope() if p.gate_status(g)["stale"]} out = report_files.write(p.dir, p.run_dir, request.form.get("by", "").strip(), stale, [g for g in gate_ids() if g not in p.scope()])"""), (""" todo=[g["id"] for g in gates if not g["approved"]], running=anything_running(p),""", """ todo=[g["id"] for g in gates if not g["approved"]], running=anything_running(p), out_of_scope=[g for g in gate_ids() if g not in p.scope()],"""), ]) patch("web/templates/gates.html", [ ("""
이 교각의 범위 밖 게이트: {{ out_of_scope|join(', ') }} (실행·승인 대상 아님)
{% endif %}범위 밖: {{ out_of_scope|join(', ') }} — 이 교각에서는 모델링하지 않습니다.
{% endif %}"""), ]) patch("engine/report_files.py", [ ("""def write(pier_dir, run_dir, author="", stale=()):""", """def write(pier_dir, run_dir, author="", stale=(), out_of_scope=()):"""), (""" md, d = report.build(pier_dir, run_dir, author, today, stale)""", """ md, d = report.build(pier_dir, run_dir, author, today, stale, out_of_scope)"""), ]) patch("engine/report.py", [ ("""def build(pier_dir, run_dir, author="", today=None, stale=()):""", """def build(pier_dir, run_dir, author="", today=None, stale=(), out_of_scope=()):"""), (""" all_ok = all(gates[g] and gates[g]["verdict"] == "PASS" and appr.get(g, {}).get("decision") == "승인" for g in gate_ids())""", """ all_ok = all(gates[g] and gates[g]["verdict"] == "PASS" and appr.get(g, {}).get("decision") == "승인" for g in gate_ids() if g not in out_of_scope)"""), (""" rows.append([g, gname, "입력 변경 — 무효(재실행 필요)" if g in stale else "미실행", "-", "-", "-"])""", """ rows.append([g, gname, "범위 밖" if g in out_of_scope else ("입력 변경 — 무효(재실행 필요)" if g in stale else "미실행"), "-", "-", "-"])"""), ]) print("patched")