Files
Aislo/E05_StabReport/E05_StabReport_Rock.py
T

215 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""E05 보고서 s11 암반검토 — E06 암반 해석 저장본(절리 · Markland · Hoek-Bray) → 시트 (PLAN 536-1).
칸 자리 = `resources/master_template/report/stab_report_slots.md` s11.
저장본이 없으면 시트를 뺌(있을 때만) — `build_program` 이 책에서 지움.
"""
from __future__ import annotations
import copy
from pathlib import Path
from typing import Any
from fastapi import HTTPException
from E05_StabReport.E05_StabReport_Cells import _set, _verdict, mark
from E06_StabRock import E06_StabRock_Model as rock_model
from E06_StabRock.E06_StabRock_Router import _result
CONDITION_NAMES = {"dry": "건기", "wet": "우기", "seismic": "지진"}
SIDE_NAMES = {"left": "좌", "right": "우"}
PROJECTION_NAMES = {"equal_angle": "등각(Wulff)", "equal_area": "등면적(Schmidt)"}
VERDICT_NAMES = {"NG": "N.G", "check": "검토", "OK": "O.K", "none": "위험 모드 없음"}
SLIDE_NAMES = {"1": "한 면({a}) 미끄러짐", "2": "한 면({b}) 미끄러짐", "12": "두 면 미끄러짐"}
COLS = "ABCDEFGH"
FIRST = 11 # 이 행부터 절리 · 측점 표를 다시 깔음
def read(project_root: Path) -> dict[str, Any] | None:
"""보고서에 쓰는 E06 결과(지금 입력 지문 것 · 없으면 가장 최근) + 낡음 표시."""
try:
out = _result(Path(project_root), None, None)
except HTTPException:
return None
return {**out["result"], "stale": out["stale"]} if out["result"] else None
def stamp(project_root: Path) -> list | None:
"""해석 지문용 — 결과 지문 · 낡음."""
rock = read(project_root)
return [rock["fingerprint"], rock["stale"]] if rock else None
def _row_styles(sheet: dict[str, Any], r: int) -> dict[str, dict]:
return {c: copy.deepcopy(sheet["칸"].get(f"{c}{r}") or {}) for c in COLS}
def _summary(rock: dict[str, Any]) -> str:
s = rock["summary"]
v = s["verdicts"]
text = (
f"깎기 비탈 {s['slopes']} · "
+ " · ".join(f"{VERDICT_NAMES[k]} {v[k]}" for k in VERDICT_NAMES if v.get(k))
+ f" — 위험 모드 평면 {s['modes']['planar']} · 쐐기 {s['modes']['wedge']} · "
f"전도 {s['modes']['toppling']}"
)
if rock["stale"]:
text += " · 입력이 해석 뒤 바뀜 — E06 [해석] 뒤 [보고서 갱신]"
return text
def fill_rock(sheet: dict[str, Any], rock: dict[str, Any], labels: dict) -> None:
"""s11 — 1. 조건 · 요약(5~9 행) · 2. 절리군(한 줄씩 · 13 행 서식) · 3. 측점(17 행 서식)."""
s = rock["settings"]
d = rock_model.DEFAULT_SETTINGS
lateral = (s["lateral_planar_deg"], s["lateral_toppling_deg"])
_set(
sheet,
"B5",
mark(
f"{PROJECTION_NAMES.get(s['projection'], s['projection'])} 하반구 · "
f"측방 한계 평면 {lateral[0]:g}° · 전도 {lateral[1]:g}°",
lateral == (d["lateral_planar_deg"], d["lateral_toppling_deg"]),
),
)
_set(
sheet,
"B6",
f"{s['gamma_r']:g} kN/m³ (사용자 입력)"
if s.get("gamma_r")
else "E02 측점 암 물성 γt (문헌 추정치 · 사용자 확정 전)",
)
_set(
sheet,
"B7",
mark(
f"인장균열 깊이 · 활동면 높이의 {s['water_fill_wet']:g} 까지 지하수 "
"(KDS 11 70 05 표 4.3-3 암반 = ½ 심도)",
s["water_fill_wet"] == d["water_fill_wet"],
),
)
crack = s["crack"]
crack_text = {
"none": "인장균열 없음",
"critical": "인장균열 = 임계(최소 안전율 자리)",
"depth": f"인장균열 깊이 {crack.get('z') or 0:g} m",
}[crack["mode"]]
bolt = s["bolt"]
bolt_text = (
f"볼트 {bolt['force']:g} (평면 kN/m · 쐐기 kN) · 수평 아래 {bolt['plunge']:g}°"
if bolt["force"]
else "볼트 없음"
)
_set(sheet, "B8", f"{crack_text} · {bolt_text}")
_set(sheet, "B9", _summary(rock))
tpl = {r: _row_styles(sheet, r) for r in (11, 12, 13, 15, 16, 17)}
merged = [m for m in sheet["병합"] if m.startswith(("A11:", "A15:"))]
for a1 in [k for k in sheet["칸"] if int("".join(ch for ch in k if ch.isdigit())) >= FIRST]:
del sheet["칸"][a1]
sheet["병합"] = [m for m in sheet["병합"] if m not in merged]
heights = {k: v for k, v in sheet["행"].items() if int(k) >= FIRST}
sheet["행"] = {k: v for k, v in sheet["행"].items() if int(k) < FIRST}
def put_row(r: int, src: int) -> None:
for c, cell in tpl[src].items():
if cell:
sheet["칸"][f"{c}{r}"] = copy.deepcopy(cell)
if str(src) in heights:
sheet["행"][str(r)] = dict(heights[str(src)])
def head(r: int, src: int) -> int:
put_row(r, src)
sheet["병합"].append(f"A{r}:H{r}")
put_row(r + 1, src + 1)
return r + 2
# 2. 절리군
r = head(FIRST, 11)
sets = _sets(rock)
for j in sets:
put_row(r, 13)
_set(sheet, f"A{r}", j.get("name") or j["id"])
for col, key in zip("BCDEF", ("dip_direction", "dip", "phi", "c", "persistence_m")):
value = j.get(key)
_set(sheet, f"{col}{r}", float(value) if value is not None else "-")
_set(sheet, f"G{r}", _station_text(labels, j.get("from_m")))
_set(sheet, f"H{r}", _station_text(labels, j.get("to_m")))
r += 1
if not sets:
put_row(r, 13)
_set(sheet, f"A{r}", "절리군 없음")
r += 1
# 3. 측점별 판정
r = head(r + 1, 15)
for entry in rock["stations"]:
r = _station_rows(sheet, put_row, r, entry, labels)
def _sets(rock: dict[str, Any]) -> list[dict[str, Any]]:
"""해석에 쓴 절리군 — 옛 저장본(sets 없음)은 측점 결과에 실린 것(지속성 · 구간 없음)."""
if "sets" in rock:
return rock["sets"]
seen: dict[str, dict] = {}
for entry in rock["stations"]:
for j in entry["joints"]:
seen.setdefault(j["id"], j)
return list(seen.values())
def _station_text(labels: dict, chainage: Any) -> Any:
if chainage is None:
return "-"
ch = float(chainage)
return labels.get(round(ch, 3)) or f"{ch:.2f}"
def _station_rows(sheet, put_row, r: int, entry: dict[str, Any], labels: dict) -> int:
slope = entry["slope"]
ch = float(entry["chainage_m"])
name = entry.get("label") or labels.get(round(ch, 3)) or f"{ch:.2f}"
side = (
f"{SIDE_NAMES.get(slope['side'], slope['side'])} · {slope['dip']:g}° / "
f"{slope['dip_direction']:g}° · 높이 {slope['height_m']:.1f} m"
)
jname = {j["id"]: j.get("name") or j["id"] for j in entry["joints"]}
rows: list[tuple[str, str, Any, Any, bool | None, str]] = []
for case in entry["cases"]:
ids = [jname.get(i, i) for i in case["ids"]]
mode = f"평면 {ids[0]}" if case["mode"] == "planar" else f"쐐기 {ids[0]} × {ids[1]}"
notes = []
if not case["formed"]:
notes.append("블록 안 생김")
if case.get("sliding") == "lift":
notes.append("들림")
elif case.get("sliding") in SLIDE_NAMES:
notes.append(SLIDE_NAMES[case["sliding"]].format(a=ids[0], b=ids[-1]))
if case.get("persistence_short"):
notes.append("지속성 < 활동면")
cond = CONDITION_NAMES.get(case["condition"], case["condition"])
rows.append(
(mode, cond, case["F"], case.get("required"), case.get("ok"), " · ".join(notes))
)
for jid in entry["kinematic"]["toppling"]:
rows.append(
(f"전도 {jname.get(jid, jid)}", "-", None, None, None, "기하 판정만 — 현장 확인")
)
if not rows:
rows.append(("없음", "-", None, None, None, "위험 모드 없음(Markland)"))
for mode, cond, fs, need, ok, note in rows:
put_row(r, 17)
_set(sheet, f"A{r}", name)
_set(sheet, f"B{r}", side)
_set(sheet, f"C{r}", mode)
_set(sheet, f"D{r}", cond)
_set(sheet, f"E{r}", float(fs) if fs is not None else "-")
_set(sheet, f"F{r}", float(need) if need is not None else "-")
if ok is None:
_set(sheet, f"G{r}", "검토" if mode.startswith("전도") else "-")
else:
_verdict(sheet, f"G{r}", ok)
_set(sheet, f"H{r}", note or None)
r += 1
return r