"""E05 보고서 s12 확률해석 — E03 확률 해석 저장본(대표 단면 · 비탈 · 조건마다 가장 최근) → 시트(PLAN 537-1). 칸 자리 = `resources/master_template/report/stab_report_slots.md` s12. 저장본이 없으면 시트를 뺌(있을 때만) — `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_Prob import load_defaults, records from E05_StabReport.E05_StabReport_Cells import _set, mark CONDITION_NAMES = {"dry": "건기", "wet": "우기", "seismic": "지진"} SIDE_NAMES = {"left": "좌", "right": "우"} ROLE_NAMES = {"cut": "깎기", "fill": "쌓기"} METHOD_NAMES = { "ordinary": "Fellenius", "bishop": "Bishop", "janbu": "Janbu 보정", "spencer": "Spencer", "morgenstern_price": "M-P", } MODE_NAMES = {"fixed": "임계면 고정", "search": "매회 탐색"} DIST_NAMES = {"normal": "정규", "lognormal": "대수정규"} COLS = "ABCDEFGHIJK" def read(project_root: Path) -> list[dict[str, Any]]: return records(Path(project_root)) def stamp(project_root: Path) -> str: """해석 지문용 — 저장본 목록 내용(표본 F 뺌).""" body = [ {**r, "result": {k: v for k, v in r["result"].items() if k != "F"}} for r in read(project_root) ] text = json.dumps(body, ensure_ascii=False, sort_keys=True, default=str) return hashlib.sha1(text.encode("utf-8")).hexdigest() def _is_default(rec: dict[str, Any], defaults: dict[str, Any]) -> bool: """요청 변수 · 표본 설정이 기본값 후보 그대로인지(「사용자 확정 전」 표기).""" req = rec["request"] props, own = defaults["properties"], defaults.get("materials") or {} for v in req["variables"]: base = { **props[v["prop"]], **((own.get(v.get("material") or "") or {}).get(v["prop"]) or {}), } spread = v.get("cov") if v.get("cov") is not None else v.get("std") want = base.get("cov") if "cov" in base else base.get("std") if ( v["dist"] != base["dist"] or spread != want or v.get("trunc_sigma") != base["trunc_sigma"] ): return False run = defaults["run"] same = all(req.get(k) == run[k] for k in ("n", "sampling", "seed", "mode", "method")) return same and req.get("rho_c_phi", 0.0) == defaults["rho_c_phi"] def fill_prob(sheet: dict[str, Any], recs: list[dict[str, Any]], labels: dict) -> None: """s12 — 1. 측점 · 비탈 · 조건마다 한 줄(6 행 서식) · 2. 변수마다 한 줄(10 행 서식 · 토네이도 순).""" tpl = { r: {c: copy.deepcopy(sheet["칸"].get(f"{c}{r}") or {}) for c in COLS} for r in (6, 8, 9, 10) } 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}" def where(rec: dict[str, Any]) -> str: return ( f"{SIDE_NAMES[rec['side']]} {ROLE_NAMES.get(rec['role'], rec['role'])} · " f"{CONDITION_NAMES.get(rec['condition'], rec['condition'])}" ) defaults = load_defaults() same = all(_is_default(rec, defaults) for rec in recs) _set(sheet, "A3", mark("변동계수 · 분포 · 표본 = 문헌 기본값 후보(근거 E03 탭)", same)) pct = lambda v: None if v is None else 100.0 * float(v) # noqa: E731 r = 6 for rec in recs: res, st = rec["result"], rec["result"]["stats"] put_row(r, tpl[6]) _set(sheet, f"A{r}", name(rec)) _set(sheet, f"B{r}", where(rec)) _set( sheet, f"C{r}", f"{METHOD_NAMES.get(res['method'], res['method'])} · {MODE_NAMES[res['mode']]}", ) _set(sheet, f"D{r}", int(st["n_valid"])) _set(sheet, f"E{r}", rec.get("fs_deterministic")) for c, k in zip("FG", ("mean", "std")): _set(sheet, f"{c}{r}", st.get(k)) _set(sheet, f"H{r}", pct(st.get("pf"))) _set(sheet, f"I{r}", st.get("beta_normal")) _set(sheet, f"J{r}", st.get("beta_lognormal")) _set(sheet, f"K{r}", pct(st.get("p_below_required"))) r += 1 r += 1 put_row(r, tpl[8]) sheet["병합"].append(f"A{r}:K{r}") sheet["행"][str(r)] = {"높이": 22} sheet["행"][str(r + 1)] = {"높이": 30} put_row(r + 1, tpl[9]) r += 2 for rec in recs: res = rec["result"] curves = {c["name"]: c for c in res["sensitivity"]["curves"]} rank = {t["name"]: i + 1 for i, t in enumerate(res["sensitivity"]["tornado"])} for v in sorted(res["variables"], key=lambda v: rank.get(v["name"], 99)): c = curves.get(v["name"]) or {} xs = c.get("x") or [None] put_row(r, tpl[10]) _set(sheet, f"A{r}", f"{name(rec)} · {where(rec)}") _set(sheet, f"B{r}", v["name"]) _set(sheet, f"C{r}", DIST_NAMES.get(v["dist"], v["dist"])) _set(sheet, f"D{r}", float(v["mean"])) _set(sheet, f"E{r}", float(v["std"])) _set(sheet, f"F{r}", v["lo"] / v["std"] if v.get("lo") is not None else "-") _set(sheet, f"G{r}", xs[0]) _set(sheet, f"H{r}", c.get("F_lo")) _set(sheet, f"I{r}", xs[-1]) _set(sheet, f"J{r}", c.get("F_hi")) _set(sheet, f"K{r}", rank.get(v["name"])) r += 1