- 측점표(s4) 오른쪽에 판정 방법 · 면 확인 · 모형 끝 면 칸 셋 + 풀이 한 줄(탐색 한계 포함) - 대표단면(s5) 조건별 방법 · 임계면 그림 · 부록 절편 = E03 판정 면(원호 Bishop · 비원호 Spencer · M-P 중 작은 값) - 입력조건 해석 방법 글 · 탐색 한계 한 줄 갱신 - 시험 534_3 판정 면 경우 덧 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
129 lines
4.6 KiB
Python
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}"
|