- 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>
210 lines
8.2 KiB
Python
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
|