Files
Aislo/E05_StabReport/E05_StabReport_Reinforce.py
T
eomsangdonandClaude Opus 5.5 8aeae00470 feat(536-3): E 보강 검토 뒷부분 — 네일 · 앵커 가정 입력 · supports 꼴 POST whatif · 최소 길이 찾기 · 횡단 그림 · 결과 표 · E05 네일 · 앵커 줄 (엔진 고리 전 = 그림만)
- logic/E03_StabSection_Support: 배치(행 수 · 첫 행 높이 · 수직 · 수평 간격 · 제원) → supports(브레인 정함 꼴 + side) · 머리 = 비탈 면 · 방향 = 활동 반대(비탈 속) · 그림 좌표(머리 · 끝 · 정착 구간 · 임계면 교점 · mode)
- UI_Support: 「네일 · 앵커 가정」 카드 · 기본값 후보(지식DB 안정성평가 02 §4 · 사용자 확정 전) · [보강 해석] · [최소 길이 찾기] · 동원력 · 지배 표 · 후 단면 mode 색
- Router_WhatIf: supports 받음 · 엔진에 supports 인자 있으면 넘김(cases[i].result.support_forces 읽음) · 없으면 supports_pending · 최소 길이 = 성긴 걸음 + 이분(0.5 m · 25 m 까지) · 지금 기울기 그대로면 원본 설계선 · 저장본 종류 support
- E05 s8: 1 절 가정 줄에 네일 · 앵커 · 3 절 네일마다 동원력 · 지배(있을 때만)
- 시험 test_536_3 14(배치 해석해 · 그림 좌표 · 엔진 전 그림만 · 가짜 엔진 최소 길이 5.0 m · E05 줄)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 04:18:18 +09:00

210 lines
8.2 KiB
Python

"""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": "활동면 안 만남",
}
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