시험 고침: 534-3 양식 시험의 시트 목록 · 쪽 수 기대(목차 · 부록 더함) · E05 시험 시트 수 7 → 9 · PDF = report_pdf(목차 쪽 번호 = 실제 · 꼬리말 · 머리말) · 부록 절편 · 측점표 쪽 나눔 · 옛 작업본 갱신 시험 덧. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
131 lines
5.7 KiB
Python
131 lines
5.7 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 E02_StabGround.E02_StabGround_Model import load_defaults
|
||
from E05_StabReport.E05_StabReport_Cells import UNCONFIRMED
|
||
from E07_StabArea.E07_StabArea_Engine import CLASS_KEYS, LEVEL_KEYS
|
||
from E07_StabArea.E07_StabArea_Router import RESULT, AreaRequest, load
|
||
|
||
LEVEL_NAMES = {"low": "낮음", "mid": "보통", "high": "높음"}
|
||
|
||
|
||
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
|
||
|
||
|
||
UNCONFIRMED_KEYS = ( # (설정 칸, 글) — 기본값 그대로면 사용자 확정 전
|
||
("c_root", "뿌리 점착 {c_root:g} kPa"),
|
||
("soil_depth_m", "토층 {soil_depth_m:g} m"),
|
||
("tr_lo", "T/R {tr_lo:g}~{tr_hi:g} m"),
|
||
("level_lo_pct", "측점 문턱 {level_lo_pct:g} % · {level_hi_pct:g} %"),
|
||
)
|
||
PAIRS = {"tr_lo": "tr_hi", "level_lo_pct": "level_hi_pct"}
|
||
|
||
|
||
def _unconfirmed(s: dict[str, Any]) -> str:
|
||
"""기본값 그대로인 설정 · 물성(E02 붕적토 문헌 추정치) — 사용자 확정 전 한 줄."""
|
||
fields = AreaRequest.model_fields
|
||
same = [
|
||
text.format(**s)
|
||
for key, text in UNCONFIRMED_KEYS
|
||
if all(
|
||
k in s and float(s[k]) == float(fields[k].default) for k in (key, PAIRS.get(key, key))
|
||
)
|
||
]
|
||
base = {m["id"]: m for m in load_defaults()["materials"]}.get(
|
||
s.get("material_id") or "colluvium"
|
||
)
|
||
if base and all(
|
||
float(s.get(k, -1)) == float(base[k]) for k in ("gamma_t", "gamma_sat", "c", "phi")
|
||
):
|
||
same.insert(0, f"토층 물성 = {base['name']} 문헌 추정치")
|
||
return f"{' · '.join(same)} — {UNCONFIRMED}" if same else "없음(모두 사용자 입력)"
|
||
|
||
|
||
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
|
||
if "station_levels" not in area["summary"]:
|
||
_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", " "))
|
||
_set(sheet, "B9", _unconfirmed(s))
|
||
for k in range(1, 7):
|
||
_set(sheet, f"C{11 + k}", float(summary["areas_m2"][CLASS_KEYS[k]]))
|
||
lo, hi = s["level_lo_pct"], s["level_hi_pct"]
|
||
bounds = {"low": f"< {lo:g} %", "mid": f"{lo:g} ~ {hi:g} %", "high": f"≥ {hi:g} %"}
|
||
for col, text in zip("EFG", ("측점 등급", "SI<1 셀 비율", "측점 수")):
|
||
_set(sheet, f"{col}11", text, 6)
|
||
for i, key in enumerate(LEVEL_KEYS):
|
||
_set(sheet, f"E{12 + i}", LEVEL_NAMES[key], 7)
|
||
_set(sheet, f"F{12 + i}", bounds[key], 7)
|
||
_set(sheet, f"G{12 + i}", int(summary["station_levels"][key]), 14)
|
||
_set(sheet, "A20", f"3. 위험 측점 — 보통 · 높음 (영향 구역 안 SI < 1 셀 비율 {lo:g} % 이상)")
|
||
_set(sheet, "E21", "측점 등급 (SI<1 비율)")
|
||
rows = [r for r in area["stations"] if r.get("level") in ("mid", "high")]
|
||
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}", f"{LEVEL_NAMES[row['level']]} ({row['risk_ratio'] * 100:.1f} %)")
|
||
_set(sheet, f"F{r}", float(row["risk_area_m2"]))
|
||
_set(sheet, f"G{r}", float(row["slope_max_deg"]))
|