Files
Aislo/E05_StabReport/E05_StabReport_Cells.py
T
eomsangdonandClaude Sonnet 5.5 cc4330d946 feat(567-1): E05 보고서 판정 면 — 측점표 판정 방법 · 면 확인 · 모형 끝 면 칸 · 대표단면 조건별 판정 면 · 탐색 한계 한 줄
- 측점표(s4) 오른쪽에 판정 방법 · 면 확인 · 모형 끝 면 칸 셋 + 풀이 한 줄(탐색 한계 포함)
- 대표단면(s5) 조건별 방법 · 임계면 그림 · 부록 절편 = E03 판정 면(원호 Bishop · 비원호 Spencer · M-P 중 작은 값)
- 입력조건 해석 방법 글 · 탐색 한계 한 줄 갱신
- 시험 534_3 판정 면 경우 덧

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 22:13:49 +09:00

129 lines
4.6 KiB
Python

"""E05 보고서 칸 쓰기 공용 — 이름표 · 서식 번호 · 칸 값 · 판정 · 기준 안전율 · 측점 이름.
(PLAN 534-3 · 534-5) 빌더 · 대표 단면(`_Section`) · 인쇄(`_Print`)가 같이 씀.
"""
from __future__ import annotations
import re
from typing import Any
from common_util.common_util_station import infer_station_interval, station_label
CASES = ("dry", "wet", "seismic")
SIDE_NAMES = {"cut": "깎기", "fill": "쌓기"}
METHOD_NAMES = {
"bishop": "Bishop",
"janbu": "Janbu 보정",
"janbu_corrected": "Janbu 보정",
"ordinary": "Fellenius",
"spencer": "Spencer",
"morgenstern_price": "Morgenstern-Price",
}
CONDITION_NAMES = {"dry": "건기", "wet": "우기", "seismic": "지진"}
REASONS = {
"fill_height": "쌓기 높이 10 m 이상",
"cut_height": "깎기 높이 15 m 이상",
"lowest": "최소 안전율 하위",
"user": "사용자 지정",
}
GROUND_NAMES = {"soil": "토사", "ripping_rock": "리핑암", "blasting_rock": "발파암", "fill": "성토"}
ST_TEXT, ST_BODY, ST_FS, ST_NG, ST_OK, ST_NUM1, ST_PROP, ST_CHAIN = 5, 7, 8, 10, 11, 13, 16, 17
A1 = re.compile(r"^([A-Z]+)(\d+)$")
def fig_key(station_key: str) -> str:
return f"fig/{station_key}.png"
UNCONFIRMED = "기본값 · 사용자 확정 전"
def mark(text: str, default: bool) -> str:
"""사용자 확정 전 표기 한 규칙 — 기본값 그대로 = 「(기본값 · 사용자 확정 전)」 · 아니면
「(사용자 입력)」."""
return f"{text} ({UNCONFIRMED if default else '사용자 입력'})"
def reach_text(reach: Any) -> str:
"""E03 탐색 한계 글 — `model_end`(기본) · `2h` · 수(m)."""
if isinstance(reach, int | float):
return f"비탈 끝 + {reach:g} m"
return "비탈 끝 + 2H" if reach == "2h" else "모형 끝"
def method_text(by_condition: dict[str, str]) -> str:
"""조건별 판정 방법 글 — 모두 같으면 방법 이름 하나 · 다르면 「건기 Bishop · 우기 M-P」."""
names = {c: METHOD_NAMES.get(m, m) for c, m in by_condition.items() if m}
if len(set(names.values())) <= 1:
return next(iter(names.values()), "-")
short = {"Morgenstern-Price": "M-P"} # 좁은 칸 — 조건마다 다를 때만 줄임
return " · ".join(f"{CONDITION_NAMES.get(c, c)} {short.get(n, n)}" for c, n in names.items())
def condition_list(flags: dict[str, Any]) -> str:
"""켜진 조건 이름 줄(건기 · 우기) — 없으면 `-`."""
return " · ".join(CONDITION_NAMES.get(c, c) for c, on in flags.items() if on) or "-"
def _set(sheet: dict[str, Any], a1: str, value: Any, style: int | None = None) -> None:
"""값(None = 지움) · 서식(None = 양식 그대로)."""
cell = dict(sheet["칸"].get(a1) or {})
cell.pop("식", None)
if value is None or value == "":
cell.pop("값", None)
else:
cell["값"] = round(value, 6) if isinstance(value, float) else value
if style is not None:
cell["서식"] = style
if cell:
sheet["칸"][a1] = cell
else:
sheet["칸"].pop(a1, None)
def _copy_row(sheet: dict[str, Any], src: int, dst: int, cols: str) -> None:
for col in cols:
cell = sheet["칸"].get(f"{col}{src}")
if cell and dst != src:
sheet["칸"][f"{col}{dst}"] = {"서식": cell["서식"]} if "서식" in cell else {}
def _shift(a1: str, dr: int) -> str:
m = A1.match(a1)
return f"{m.group(1)}{int(m.group(2)) + dr}"
def _verdict(sheet: dict[str, Any], a1: str, ok: bool | None) -> None:
if ok is None:
_set(sheet, a1, None)
else:
_set(sheet, a1, "O.K" if ok else "N.G", ST_OK if ok else ST_NG)
def _criterion(ground: dict[str, Any], case: str, side: str | None = None) -> float:
sf = (ground.get("safety_factors") or {}).get(case) or {}
if side in ("cut", "fill"):
return float(sf[side])
return max(float(sf["cut"]), float(sf["fill"])) # 표 하나 = 엄한 쪽
def _seismic(ground: dict[str, Any]) -> bool:
return bool((ground.get("seismic") or {}).get("enabled"))
def _labels(stations: list[dict[str, Any]]) -> dict[float, str]:
"""누가 → 측점 이름(`61+14.5` · 간격 = 측점 목록 최빈 · 복사본 label 안 씀)."""
interval = infer_station_interval(stations)
return {
round(float(s["chainage_m"]), 3): station_label(float(s["chainage_m"]), interval)
for s in stations
}
def _label_at(labels: dict[float, str], chainage: float) -> str:
if not labels:
return f"{chainage:.2f}"
near = min(labels, key=lambda c: abs(c - chainage))
return labels[near] or f"{chainage:.2f}"