"""E05 보고서 칸 쓰기 공용 — 이름표 · 서식 번호 · 칸 값 · 판정 · 기준 안전율 · 측점 이름. (PLAN 534-3 · 534-5) 빌더 · 대표 단면(`_Section`) · 인쇄(`_Print`)가 같이 씀. """ from __future__ import annotations import re from typing import Any CASES = ("dry", "wet", "seismic") SIDE_NAMES = {"cut": "깎기", "fill": "쌓기"} METHOD_NAMES = { "bishop": "Bishop", "janbu": "Janbu 보정", "janbu_corrected": "Janbu 보정", "ordinary": "Fellenius", "spencer": "Spencer", } 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 _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]: return {round(float(s["chainage_m"]), 3): s.get("label") or "" 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}"