"""E05 보고서 s8 보강검토 — E03 보강 검토 저장본(비탈 완화 · 소단 · 네일 · 앵커 가정 · 벽 외적 안정) → 시트 (PLAN 536-3). 칸 자리 = `resources/master_template/report/stab_report_slots.md` s8. 저장본이 없으면 시트를 뺌(있을 때만) — `build_program` 이 책에서 지움. """ from __future__ import annotations import copy import hashlib import json from pathlib import Path from typing import Any from E03_StabSection.E03_StabSection_Router_WhatIf import records CONDITION_NAMES = {"dry": "건기", "wet": "우기", "seismic": "지진"} SIDE_NAMES = {"left": "좌", "right": "우"} ROLE_NAMES = {"cut": "깎기", "fill": "쌓기"} CASE_NAMES = {"normal": "평상시", "seismic": "지진 시"} CHECKS = ("sliding", "overturning", "bearing") SUPPORT_NAMES = {"nail": "네일", "anchor": "앵커"} MODE_NAMES = { "tensile": "인장", "pullout": "인발", "stripping": "머리 쪽 빠짐", "not_crossing": "활동면 안 만남", "prestress": "선인장(능동)", } SUPPORT_HEAD = ("측점", "비탈 · 조건", "번호 · 종류", "머리 x / z", "길이 m", "각도 °", "간격 m") ST_NG, ST_OK = 10, 11 def read(project_root: Path) -> list[dict[str, Any]]: return records(Path(project_root)) def stamp(project_root: Path) -> str: """해석 지문용 — 저장본 목록 내용.""" body = json.dumps(read(project_root), ensure_ascii=False, sort_keys=True, default=str) return hashlib.sha1(body.encode("utf-8")).hexdigest() def _set(sheet: dict[str, Any], a1: str, value: Any, style: int | None = None) -> None: cell = dict(sheet["칸"].get(a1) or {}) cell.pop("식", None) if value is None: cell.pop("값", None) else: cell["값"] = round(value, 6) if isinstance(value, float) else value if style is not None: cell["서식"] = style sheet["칸"][a1] = cell def _verdict(sheet: dict[str, Any], a1: str, ok: bool | None) -> None: if ok is None: _set(sheet, a1, "-") else: _set(sheet, a1, "O.K" if ok else "N.G", ST_OK if ok else ST_NG) def _support_text(rec: dict[str, Any]) -> str: sup = rec["assume"]["supports"] first = sup[0] text = ( f"{SUPPORT_NAMES[first['type']]} {len(sup)}개 · L {first['length_m']:g} m · " f"{first['angle_deg']:g}° · @{first['spacing_m']:g} m" ) search = rec.get("search") if search: goal = CONDITION_NAMES.get(search.get("condition"), "전 조건") text += ( f" · 최소 길이({goal} 목표)" if search.get("found") else f" · 최소 길이 못 찾음({goal} · {search.get('longest', 0):g} m)" ) if rec.get("supports_pending"): text += " · 보강 엔진 연결 전(보강 없는 값)" return text def _assume_text(rec: dict[str, Any]) -> str: if rec.get("type") == "support": return _support_text(rec) a = rec["assume"] text = f"1:{a['ratio_before']:g} → 1:{a['ratio']:g}" if a.get("bench_w") and a.get("bench_h"): text += f" · 소단 폭 {a['bench_w']:g} m / 높이 {a['bench_h']:g} m 마다" search = rec.get("search") if search: goal = CONDITION_NAMES.get(search.get("condition"), "전 조건") text += ( f" · 최소 완화({goal} 목표)" if search.get("found") else f" · 최소 완화 못 찾음({goal} · 끝값)" ) return text def fill_reinforce(sheet: dict[str, Any], recs: list[dict[str, Any]], labels: dict) -> None: """s8 — 1. 가정 줄(조건마다 한 줄 · 6 행 서식) · 2. 벽 줄(경우마다 한 줄 · 10 행 서식).""" tpl = { r: {c: copy.deepcopy(sheet["칸"].get(f"{c}{r}") or {}) for c in "ABCDEFGHI"} for r in (8, 9, 10) } row6 = {c: copy.deepcopy(sheet["칸"].get(f"{c}6") or {}) for c in "ABCDEFGHI"} for a1 in [k for k in sheet["칸"] if int("".join(ch for ch in k if ch.isdigit())) >= 6]: del sheet["칸"][a1] sheet["병합"] = [ m for m in sheet["병합"] if int("".join(ch for ch in m.split(":")[0] if ch.isdigit())) < 6 ] sheet["행"] = {k: v for k, v in sheet["행"].items() if int(k) < 6} def put_row(r: int, styles: dict[str, dict]) -> None: for c, cell in styles.items(): sheet["칸"][f"{c}{r}"] = copy.deepcopy(cell) def name(rec: dict[str, Any]) -> str: ch = float(rec["chainage_m"]) return rec.get("label") or labels.get(round(ch, 3)) or f"{ch:.2f}" r = 6 slopes = [x for x in recs if x["type"] in ("slope", "support")] if any(x["type"] == "support" for x in slopes): _set(sheet, "A4", "1. 비탈 완화 · 소단 · 네일 · 앵커 가정 — 전후 안전율 (Bishop)") for rec in slopes: a = rec["assume"] for cond, after in rec["after"].items(): before = rec["before"].get(cond) or {} put_row(r, row6) _set(sheet, f"A{r}", name(rec)) _set(sheet, f"B{r}", f"{SIDE_NAMES[a['side']]} · {ROLE_NAMES[a['role']]}") _set(sheet, f"C{r}", _assume_text(rec)) _set(sheet, f"D{r}", CONDITION_NAMES.get(cond, cond)) _set(sheet, f"E{r}", before.get("fs")) _set(sheet, f"F{r}", after.get("fs")) _set(sheet, f"G{r}", after.get("required")) _verdict(sheet, f"H{r}", before.get("ok")) _verdict(sheet, f"I{r}", after.get("ok")) r += 1 if not slopes: put_row(r, row6) _set(sheet, f"A{r}", "가정 없음") r += 1 r += 1 put_row(r, tpl[8]) sheet["병합"].append(f"A{r}:I{r}") sheet["행"][str(r)] = sheet["행"][str(r + 1)] = {"높이": 22} put_row(r + 1, tpl[9]) r += 2 walls = [x for x in recs if x["type"] == "wall"] for rec in walls: p = rec["params"] for wall in rec["walls"]: for case in wall["cases"]: put_row(r, tpl[10]) _set(sheet, f"A{r}", name(rec)) _set( sheet, f"B{r}", f"{wall.get('name') or '벽'} · {CASE_NAMES[case['case']]} · " f"{'Coulomb' if p['method'] == 'coulomb' else 'Rankine'} · 수위 {p['water_ratio']:g}H", ) for c, need, check in zip("CEG", "DFH", CHECKS): fs = case["fs"][check] _set(sheet, f"{c}{r}", fs if fs == fs and abs(fs) < 1e6 else None) _set(sheet, f"{need}{r}", case["required"][check]) _verdict(sheet, f"I{r}", all(case["ok"].values())) r += 1 if not walls: put_row(r, tpl[10]) _set(sheet, f"A{r}", "벽 검토 없음") r += 1 supports = [x for x in recs if x["type"] == "support"] if not supports: return r += 1 put_row(r, tpl[8]) _set(sheet, f"A{r}", "3. 네일 · 앵커 — 네일마다 동원력 · 지배 (단위 폭 = 힘 / 간격)") sheet["병합"].append(f"A{r}:I{r}") sheet["행"][str(r)] = sheet["행"][str(r + 1)] = {"높이": 22} put_row(r + 1, tpl[9]) for c, text in zip("ABCDEFGHI", (*SUPPORT_HEAD, "동원력 kN/m", "지배")): _set(sheet, f"{c}{r + 1}", text) r += 2 for rec in supports: a = rec["assume"] forces = rec.get("support_forces") or {} for cond in rec["after"]: by = {f["index"]: f for f in forces.get(cond) or []} for i, sup in enumerate(a["supports"]): f = by.get(i) put_row(r, tpl[10]) _set(sheet, f"A{r}", name(rec)) _set( sheet, f"B{r}", f"{SIDE_NAMES[a['side']]} · {ROLE_NAMES[a['role']]} · " f"{CONDITION_NAMES.get(cond, cond)}", ) _set(sheet, f"C{r}", f"{i + 1} · {SUPPORT_NAMES[sup['type']]}") _set(sheet, f"D{r}", f"{sup['x']:.2f} / {sup['z']:.2f}") _set(sheet, f"E{r}", float(sup["length_m"])) _set(sheet, f"F{r}", float(sup["angle_deg"])) _set(sheet, f"G{r}", float(sup["spacing_m"])) _set(sheet, f"H{r}", float(f["mobilized_kN_per_m"]) if f else None) _set(sheet, f"I{r}", MODE_NAMES.get(f["mode"], f["mode"]) if f else "-") r += 1