- 엔진: 포화비 W = min(x·a/sinβ, 1) · x = R/T 범위 · SI = 하한(c · φ 아래 · 가장 젖음) ≥ 1 → 그 값 · 상한 < 1 → 0 · 사이 = P(Fs > 1)(c · tanφ 닫힌 꼴 · x 중점 적분 20 마디) - 옛 「강우 강도(정상류)」 시나리오 뺌 — 첨두 강우를 정상류 함양으로 넣어 거의 다 포화 · T/R 로 갈음 · 엔진 판 e07-2(옛 결과 = 다시 해석) - 기본값 출처 = SINMAP 2 설명서 기본 보정 · 국내 적용 연구(뿌리 0~5 kPa) · 지식DB 예정노선/02 · 바꿀 수 있음 - 시험 536-2: 강우 포화비 시험을 습윤지수 · SI 하한/상한 Pack 원식 · 시나리오 둘로 바꿈 · 534-3 광역요약 B7 글 맞춤 - 검증 노선: 위험(SI<1) 91.7 % → 11.1 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
86 lines
3.6 KiB
Python
86 lines
3.6 KiB
Python
"""E05 보고서 s7 광역요약 — E07 광역 자연사면 위험 저장본 → 시트 (PLAN 536-2).
|
||
|
||
칸 자리 = `resources/master_template/report/stab_report_slots.md` s7.
|
||
E07 저장본이 없으면 「광역 해석 전」.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from E07_StabArea.E07_StabArea_Engine import CLASS_KEYS, RISK_CLASS
|
||
from E07_StabArea.E07_StabArea_Router import RESULT, load
|
||
|
||
CLASS_NAMES = ("", "안정", "보통 안정", "준안정", "위험 하한", "위험 상한", "불안정")
|
||
|
||
|
||
def scenario_text(s: dict[str, Any]) -> str:
|
||
"""시나리오 글 — 화면 시나리오 이름과 같은 말."""
|
||
if s.get("scenario") == "saturated":
|
||
return "최악 — 완전 포화(m = 1 · 강우 극한)"
|
||
if s.get("scenario") == "sinmap":
|
||
return (
|
||
f"표준 — SINMAP 습윤지수 W = min(R·a / (T·sinθ), 1) · "
|
||
f"T/R {s['tr_lo']:g}~{s['tr_hi']:g} m"
|
||
)
|
||
return f"옛 조건({s.get('scenario')}) — E07 에서 [다시 해석]"
|
||
|
||
|
||
def stamp(project_root: Path) -> list[int] | None:
|
||
"""해석 지문용 — 저장본 크기 · 고친 때."""
|
||
path = Path(project_root) / RESULT
|
||
return [path.stat().st_size, path.stat().st_mtime_ns] if path.is_file() else None
|
||
|
||
|
||
def read(project_root: Path) -> dict[str, Any] | None:
|
||
"""저장본에서 보고서에 쓰는 것만(격자 배열 뺌)."""
|
||
doc = load(project_root)
|
||
if not doc:
|
||
return None
|
||
return {k: doc[k] for k in ("settings", "computed_at", "stations", "summary")}
|
||
|
||
|
||
def _set(sheet: dict[str, Any], a1: str, value: Any, style: int | None = None) -> None:
|
||
cell = dict(sheet["칸"].get(a1) or {})
|
||
cell["값"] = round(value, 6) if isinstance(value, float) else value
|
||
if style is not None:
|
||
cell["서식"] = style
|
||
sheet["칸"][a1] = cell
|
||
|
||
|
||
def fill_area(sheet: dict[str, Any], area: dict[str, Any] | None, labels: dict[float, str]) -> None:
|
||
"""s7 — 조건 넷 · 등급별 면적 여섯 · 위험 측점(22 행부터 한 줄씩)."""
|
||
if not area:
|
||
_set(sheet, "B5", "광역 해석 전 — E07 광역에서 [해석] 뒤 [보고서 갱신]")
|
||
return
|
||
s, summary = area["settings"], area["summary"]
|
||
_set(sheet, "B5", f"중심선 ± {s['band_m']:g} m · 격자 {s['cell_m']:g} m")
|
||
_set(
|
||
sheet,
|
||
"B6",
|
||
f"γt {s['gamma_t']:g} · γsat {s['gamma_sat']:g} kN/m³ · c {s['c']:g}~{s['c_hi']:g} kPa · "
|
||
f"φ {s['phi']:g}~{s['phi_hi']:g}° · 뿌리 점착 {s['c_root']:g} kPa · "
|
||
f"토층 {s['soil_depth_m']:g} m",
|
||
)
|
||
_set(sheet, "B7", scenario_text(s))
|
||
_set(sheet, "B8", str(area["computed_at"]).replace("T", " "))
|
||
for k in range(1, 7):
|
||
_set(sheet, f"C{11 + k}", float(summary["areas_m2"][CLASS_KEYS[k]]))
|
||
rows = [r for r in area["stations"] if r.get("worst_class", 0) >= RISK_CLASS]
|
||
template = {col: dict(sheet["칸"].get(f"{col}22") or {}) for col in "ABCDEFG"}
|
||
if not rows:
|
||
_set(sheet, "A22", "위험 측점 없음")
|
||
for i, row in enumerate(rows):
|
||
r = 22 + i
|
||
for col, cell in template.items():
|
||
sheet["칸"][f"{col}{r}"] = dict(cell)
|
||
ch = float(row["chainage_m"])
|
||
_set(sheet, f"A{r}", row.get("label") or labels.get(round(ch, 3)) or f"{ch:.2f}")
|
||
_set(sheet, f"B{r}", ch)
|
||
_set(sheet, f"C{r}", float(row["fs_min"]))
|
||
_set(sheet, f"D{r}", float(row["si_min"]))
|
||
_set(sheet, f"E{r}", CLASS_NAMES[int(row["worst_class"])])
|
||
_set(sheet, f"F{r}", float(row["risk_area_m2"]))
|
||
_set(sheet, f"G{r}", float(row["slope_max_deg"]))
|