feat(E05): 보고서 「확률해석」 시트(있을 때만) — 측점 · 비탈 · 조건마다 결정론 Fs · 평균 · σ · Pf · β · P(F<기준) · 변수 토네이도 순 · 양식 s12 · 자리표 (537-1 · 요구 351)
- E05_StabReport_Prob(새 파일): E03 확률 저장본(가장 최근) → s12 · 기본값 후보 그대로면 「사용자 확정 전」 · 아니면 「사용자 입력」 - Build: 입력 · 해석 지문 · 프로그램 몫 s12 · 없으면 시트 뺌 · Print 목차 제목 - 양식 stab_report.json s12(가로 · A~K) · stab_report_slots.md s12 · 양식 시험 시트 차례 · 빈 판 쪽 수 +1 - 시험 test_537_1_e03_prob_route: E05 시트 값 = 저장본 · 없으면 뺌 · 화면 계산 TS 요청 = 서버 ProbRequest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,14 +3,14 @@
|
||||
입력 = E02 지반 조건(`ground.json`) · E03 2D 파생 저장본(`stab2d_summary` 가장 최근 · 측점별
|
||||
`stab2d_station`) · 대표 단면(E03 자동 + `representative.json` 켬/끔) · E04 3D 저장본 요약
|
||||
(있으면) · E07 광역 저장본(있으면) · E03 보강 검토 저장본(있으면 · 없으면 s8 뺌) ·
|
||||
E06 암반 해석 저장본(있으면 · 없으면 s11 뺌) ·
|
||||
E06 암반 해석 저장본(있으면 · 없으면 s11 뺌) · E03 확률 해석 저장본(있으면 · 없으면 s12 뺌) ·
|
||||
E01 복사본(측점 · 횡단 · 노선).
|
||||
양식 = `resources/master_template/report/stab_report.json`
|
||||
칸 자리 = 같은 폴더 `stab_report_slots.md`.
|
||||
|
||||
몫: 사용자(표지 s1 · 개요 s2) = 처음 만들 때만 채움 ·
|
||||
프로그램(목차 s9 · 입력조건 s3 · 측점표 s4 · 대표단면 s5 · 3D요약 s6 · 광역요약 s7 · 보강검토 s8 ·
|
||||
암반검토 s11 · 부록_절편 s10) = [보고서 갱신] 때 양식에서 통째로 다시 씀. 대표 단면 · 부록 = `_Section` ·
|
||||
암반검토 s11 · 확률해석 s12 · 부록_절편 s10) = [보고서 갱신] 때 양식에서 통째로 다시 씀. 대표 단면 · 부록 = `_Section` ·
|
||||
목차 쪽 번호 · 머리말/꼬리말 · 긴 표 쪽 나눔 = `_Print` · 칸 공용 = `_Cells`.
|
||||
판정 글 칸 = 엔진 판정 값 + N.G 서식 10 · O.K 서식 11 을 직접 붙임
|
||||
(인쇄 HTML · xlsx 는 조건부 서식을 안 먹음).
|
||||
@@ -38,6 +38,7 @@ from E04_Stab3D import E04_Stab3D_Run as run3d
|
||||
from E05_StabReport import E05_StabReport_Area as area
|
||||
from E05_StabReport import E05_StabReport_Figure as figure
|
||||
from E05_StabReport import E05_StabReport_Print as printer
|
||||
from E05_StabReport import E05_StabReport_Prob as prob
|
||||
from E05_StabReport import E05_StabReport_Reinforce as reinforce
|
||||
from E05_StabReport import E05_StabReport_Rock as rock
|
||||
from E05_StabReport import E05_StabReport_Section as section
|
||||
@@ -62,7 +63,7 @@ FORM_PATH = ROOT / "resources" / "master_template" / "report" / "stab_report.jso
|
||||
STAGE = "E05_StabReport"
|
||||
REPORT, META = "report.json", "report_meta.json"
|
||||
USER_SHEETS = ("s1", "s2")
|
||||
PROGRAM_SHEETS = ("s9", "s3", "s4", "s5", "s6", "s7", "s8", "s11", "s10")
|
||||
PROGRAM_SHEETS = ("s9", "s3", "s4", "s5", "s6", "s7", "s8", "s11", "s12", "s10")
|
||||
REPRESENTATIVE = Path("E03_StabSection") / "representative.json"
|
||||
BAND_KEY = "fig/3d_band.png"
|
||||
|
||||
@@ -170,6 +171,7 @@ def analysis_fingerprint(project_root: Path) -> str:
|
||||
"area": area.stamp(project_root),
|
||||
"reinforce": reinforce.stamp(project_root),
|
||||
"rock": rock.stamp(project_root),
|
||||
"prob": prob.stamp(project_root),
|
||||
"form": hashlib.sha1(FORM_PATH.read_bytes()).hexdigest(),
|
||||
}
|
||||
text = json.dumps(body, ensure_ascii=False, sort_keys=True, default=str)
|
||||
@@ -221,6 +223,7 @@ def gather(project_root: Path) -> dict[str, Any]:
|
||||
"area": area.read(project_root),
|
||||
"reinforce": reinforce.read(project_root),
|
||||
"rock": rock.read(project_root),
|
||||
"prob": prob.read(project_root),
|
||||
"fingerprint": analysis_fingerprint(project_root),
|
||||
}
|
||||
|
||||
@@ -453,6 +456,10 @@ def build_program(book: dict[str, Any], inputs: dict[str, Any]) -> list[tuple[st
|
||||
rock.fill_rock(by["s11"], inputs["rock"], _labels(inputs["stations"]))
|
||||
else: # 암반 해석이 없으면 시트를 뺌
|
||||
book["시트"] = [s for s in book["시트"] if s["id"] != "s11"]
|
||||
if inputs.get("prob"):
|
||||
prob.fill_prob(by["s12"], inputs["prob"], _labels(inputs["stations"]))
|
||||
else: # 확률 해석이 없으면 시트를 뺌
|
||||
book["시트"] = [s for s in book["시트"] if s["id"] != "s12"]
|
||||
printer.fill_toc(by["s9"], book, None) # 쪽 번호 = 저장 전 `printer.number_toc`
|
||||
return jobs
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ TITLES = {
|
||||
"s7": "광역 자연사면 위험 요약",
|
||||
"s8": "보강 검토 — 비탈 완화 · 소단 · 벽 외적 안정",
|
||||
"s11": "암반 비탈면 검토 — 평사투영 · 평면 · 쐐기 안전율",
|
||||
"s12": "확률 해석 · 민감도 — 파괴확률 · 신뢰도 지수",
|
||||
"s10": "대표 단면 절편 계산표",
|
||||
}
|
||||
APPENDIX = {"s10"}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
"""E05 보고서 s12 확률해석 — E03 확률 해석 저장본(대표 단면 · 비탈 · 조건마다 가장 최근) → 시트(PLAN 537-1).
|
||||
|
||||
칸 자리 = `resources/master_template/report/stab_report_slots.md` s12.
|
||||
저장본이 없으면 시트를 뺌(있을 때만) — `build_program` 이 책에서 지움.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from E03_StabSection.E03_StabSection_Router_Prob import load_defaults, records
|
||||
from E05_StabReport.E05_StabReport_Cells import _set, mark
|
||||
|
||||
CONDITION_NAMES = {"dry": "건기", "wet": "우기", "seismic": "지진"}
|
||||
SIDE_NAMES = {"left": "좌", "right": "우"}
|
||||
ROLE_NAMES = {"cut": "깎기", "fill": "쌓기"}
|
||||
METHOD_NAMES = {
|
||||
"ordinary": "Fellenius",
|
||||
"bishop": "Bishop",
|
||||
"janbu": "Janbu 보정",
|
||||
"spencer": "Spencer",
|
||||
"morgenstern_price": "M-P",
|
||||
}
|
||||
MODE_NAMES = {"fixed": "임계면 고정", "search": "매회 탐색"}
|
||||
DIST_NAMES = {"normal": "정규", "lognormal": "대수정규"}
|
||||
COLS = "ABCDEFGHIJK"
|
||||
|
||||
|
||||
def read(project_root: Path) -> list[dict[str, Any]]:
|
||||
return records(Path(project_root))
|
||||
|
||||
|
||||
def stamp(project_root: Path) -> str:
|
||||
"""해석 지문용 — 저장본 목록 내용(표본 F 뺌)."""
|
||||
body = [
|
||||
{**r, "result": {k: v for k, v in r["result"].items() if k != "F"}}
|
||||
for r in read(project_root)
|
||||
]
|
||||
text = json.dumps(body, ensure_ascii=False, sort_keys=True, default=str)
|
||||
return hashlib.sha1(text.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def _is_default(rec: dict[str, Any], defaults: dict[str, Any]) -> bool:
|
||||
"""요청 변수 · 표본 설정이 기본값 후보 그대로인지(「사용자 확정 전」 표기)."""
|
||||
req = rec["request"]
|
||||
props, own = defaults["properties"], defaults.get("materials") or {}
|
||||
for v in req["variables"]:
|
||||
base = {
|
||||
**props[v["prop"]],
|
||||
**((own.get(v.get("material") or "") or {}).get(v["prop"]) or {}),
|
||||
}
|
||||
spread = v.get("cov") if v.get("cov") is not None else v.get("std")
|
||||
want = base.get("cov") if "cov" in base else base.get("std")
|
||||
if (
|
||||
v["dist"] != base["dist"]
|
||||
or spread != want
|
||||
or v.get("trunc_sigma") != base["trunc_sigma"]
|
||||
):
|
||||
return False
|
||||
run = defaults["run"]
|
||||
same = all(req.get(k) == run[k] for k in ("n", "sampling", "seed", "mode", "method"))
|
||||
return same and req.get("rho_c_phi", 0.0) == defaults["rho_c_phi"]
|
||||
|
||||
|
||||
def fill_prob(sheet: dict[str, Any], recs: list[dict[str, Any]], labels: dict) -> None:
|
||||
"""s12 — 1. 측점 · 비탈 · 조건마다 한 줄(6 행 서식) · 2. 변수마다 한 줄(10 행 서식 · 토네이도 순)."""
|
||||
tpl = {
|
||||
r: {c: copy.deepcopy(sheet["칸"].get(f"{c}{r}") or {}) for c in COLS} for r in (6, 8, 9, 10)
|
||||
}
|
||||
for a1 in [k for k in sheet["칸"] if int("".join(ch for ch in k if ch.isdigit())) >= 6]:
|
||||
del sheet["칸"][a1]
|
||||
sheet["병합"] = [
|
||||
m for m in sheet["병합"] if int("".join(ch for ch in m.split(":")[0] if ch.isdigit())) < 6
|
||||
]
|
||||
sheet["행"] = {k: v for k, v in sheet["행"].items() if int(k) < 6}
|
||||
|
||||
def put_row(r: int, styles: dict[str, dict]) -> None:
|
||||
for c, cell in styles.items():
|
||||
sheet["칸"][f"{c}{r}"] = copy.deepcopy(cell)
|
||||
|
||||
def name(rec: dict[str, Any]) -> str:
|
||||
ch = float(rec["chainage_m"])
|
||||
return rec.get("label") or labels.get(round(ch, 3)) or f"{ch:.2f}"
|
||||
|
||||
def where(rec: dict[str, Any]) -> str:
|
||||
return (
|
||||
f"{SIDE_NAMES[rec['side']]} {ROLE_NAMES.get(rec['role'], rec['role'])} · "
|
||||
f"{CONDITION_NAMES.get(rec['condition'], rec['condition'])}"
|
||||
)
|
||||
|
||||
defaults = load_defaults()
|
||||
same = all(_is_default(rec, defaults) for rec in recs)
|
||||
_set(sheet, "A3", mark("변동계수 · 분포 · 표본 = 문헌 기본값 후보(근거 E03 탭)", same))
|
||||
pct = lambda v: None if v is None else 100.0 * float(v) # noqa: E731
|
||||
r = 6
|
||||
for rec in recs:
|
||||
res, st = rec["result"], rec["result"]["stats"]
|
||||
put_row(r, tpl[6])
|
||||
_set(sheet, f"A{r}", name(rec))
|
||||
_set(sheet, f"B{r}", where(rec))
|
||||
_set(
|
||||
sheet,
|
||||
f"C{r}",
|
||||
f"{METHOD_NAMES.get(res['method'], res['method'])} · {MODE_NAMES[res['mode']]}",
|
||||
)
|
||||
_set(sheet, f"D{r}", int(st["n_valid"]))
|
||||
_set(sheet, f"E{r}", rec.get("fs_deterministic"))
|
||||
for c, k in zip("FG", ("mean", "std")):
|
||||
_set(sheet, f"{c}{r}", st.get(k))
|
||||
_set(sheet, f"H{r}", pct(st.get("pf")))
|
||||
_set(sheet, f"I{r}", st.get("beta_normal"))
|
||||
_set(sheet, f"J{r}", st.get("beta_lognormal"))
|
||||
_set(sheet, f"K{r}", pct(st.get("p_below_required")))
|
||||
r += 1
|
||||
r += 1
|
||||
put_row(r, tpl[8])
|
||||
sheet["병합"].append(f"A{r}:K{r}")
|
||||
sheet["행"][str(r)] = {"높이": 22}
|
||||
sheet["행"][str(r + 1)] = {"높이": 30}
|
||||
put_row(r + 1, tpl[9])
|
||||
r += 2
|
||||
for rec in recs:
|
||||
res = rec["result"]
|
||||
curves = {c["name"]: c for c in res["sensitivity"]["curves"]}
|
||||
rank = {t["name"]: i + 1 for i, t in enumerate(res["sensitivity"]["tornado"])}
|
||||
for v in sorted(res["variables"], key=lambda v: rank.get(v["name"], 99)):
|
||||
c = curves.get(v["name"]) or {}
|
||||
xs = c.get("x") or [None]
|
||||
put_row(r, tpl[10])
|
||||
_set(sheet, f"A{r}", f"{name(rec)} · {where(rec)}")
|
||||
_set(sheet, f"B{r}", v["name"])
|
||||
_set(sheet, f"C{r}", DIST_NAMES.get(v["dist"], v["dist"]))
|
||||
_set(sheet, f"D{r}", float(v["mean"]))
|
||||
_set(sheet, f"E{r}", float(v["std"]))
|
||||
_set(sheet, f"F{r}", v["lo"] / v["std"] if v.get("lo") is not None else "-")
|
||||
_set(sheet, f"G{r}", xs[0])
|
||||
_set(sheet, f"H{r}", c.get("F_lo"))
|
||||
_set(sheet, f"I{r}", xs[-1])
|
||||
_set(sheet, f"J{r}", c.get("F_hi"))
|
||||
_set(sheet, f"K{r}", rank.get(v["name"]))
|
||||
r += 1
|
||||
@@ -4133,6 +4133,452 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "s12",
|
||||
"이름": "확률해석",
|
||||
"칸": {
|
||||
"A1": {
|
||||
"서식": 1,
|
||||
"값": "확률 해석 · 민감도 (대표 단면)"
|
||||
},
|
||||
"B1": {
|
||||
"서식": 1
|
||||
},
|
||||
"C1": {
|
||||
"서식": 1
|
||||
},
|
||||
"D1": {
|
||||
"서식": 1
|
||||
},
|
||||
"E1": {
|
||||
"서식": 1
|
||||
},
|
||||
"F1": {
|
||||
"서식": 1
|
||||
},
|
||||
"G1": {
|
||||
"서식": 1
|
||||
},
|
||||
"H1": {
|
||||
"서식": 1
|
||||
},
|
||||
"I1": {
|
||||
"서식": 1
|
||||
},
|
||||
"J1": {
|
||||
"서식": 1
|
||||
},
|
||||
"K1": {
|
||||
"서식": 1
|
||||
},
|
||||
"A2": {
|
||||
"서식": 2,
|
||||
"값": "물성 = 확률 변수(문헌 추정 변동계수) · Pf = P(Fs < 1) · β 정규 = (μ − 1)/σ · β 대수정규 = ln(μ/√(1+V²))/√ln(1+V²) — Slide2 · SLOPE/W Probabilistic 과 같은 정의"
|
||||
},
|
||||
"B2": {
|
||||
"서식": 2
|
||||
},
|
||||
"C2": {
|
||||
"서식": 2
|
||||
},
|
||||
"D2": {
|
||||
"서식": 2
|
||||
},
|
||||
"E2": {
|
||||
"서식": 2
|
||||
},
|
||||
"F2": {
|
||||
"서식": 2
|
||||
},
|
||||
"G2": {
|
||||
"서식": 2
|
||||
},
|
||||
"H2": {
|
||||
"서식": 2
|
||||
},
|
||||
"I2": {
|
||||
"서식": 2
|
||||
},
|
||||
"J2": {
|
||||
"서식": 2
|
||||
},
|
||||
"K2": {
|
||||
"서식": 2
|
||||
},
|
||||
"A3": {
|
||||
"서식": 12
|
||||
},
|
||||
"B3": {
|
||||
"서식": 12
|
||||
},
|
||||
"C3": {
|
||||
"서식": 12
|
||||
},
|
||||
"D3": {
|
||||
"서식": 12
|
||||
},
|
||||
"E3": {
|
||||
"서식": 12
|
||||
},
|
||||
"F3": {
|
||||
"서식": 12
|
||||
},
|
||||
"G3": {
|
||||
"서식": 12
|
||||
},
|
||||
"H3": {
|
||||
"서식": 12
|
||||
},
|
||||
"I3": {
|
||||
"서식": 12
|
||||
},
|
||||
"J3": {
|
||||
"서식": 12
|
||||
},
|
||||
"K3": {
|
||||
"서식": 12
|
||||
},
|
||||
"A4": {
|
||||
"서식": 3,
|
||||
"값": "1. 측점별 결과 — 안전율 분포 · 파괴확률 · 신뢰도 지수"
|
||||
},
|
||||
"B4": {
|
||||
"서식": 3
|
||||
},
|
||||
"C4": {
|
||||
"서식": 3
|
||||
},
|
||||
"D4": {
|
||||
"서식": 3
|
||||
},
|
||||
"E4": {
|
||||
"서식": 3
|
||||
},
|
||||
"F4": {
|
||||
"서식": 3
|
||||
},
|
||||
"G4": {
|
||||
"서식": 3
|
||||
},
|
||||
"H4": {
|
||||
"서식": 3
|
||||
},
|
||||
"I4": {
|
||||
"서식": 3
|
||||
},
|
||||
"J4": {
|
||||
"서식": 3
|
||||
},
|
||||
"K4": {
|
||||
"서식": 3
|
||||
},
|
||||
"A5": {
|
||||
"서식": 6,
|
||||
"값": "측점"
|
||||
},
|
||||
"B5": {
|
||||
"서식": 6,
|
||||
"값": "비탈 · 조건"
|
||||
},
|
||||
"C5": {
|
||||
"서식": 6,
|
||||
"값": "방법 · 임계면"
|
||||
},
|
||||
"D5": {
|
||||
"서식": 6,
|
||||
"값": "표본"
|
||||
},
|
||||
"E5": {
|
||||
"서식": 6,
|
||||
"값": "결정론 Fs"
|
||||
},
|
||||
"F5": {
|
||||
"서식": 6,
|
||||
"값": "Fs 평균"
|
||||
},
|
||||
"G5": {
|
||||
"서식": 6,
|
||||
"값": "표준편차"
|
||||
},
|
||||
"H5": {
|
||||
"서식": 6,
|
||||
"값": "Pf %"
|
||||
},
|
||||
"I5": {
|
||||
"서식": 6,
|
||||
"값": "β 정규"
|
||||
},
|
||||
"J5": {
|
||||
"서식": 6,
|
||||
"값": "β 대수정규"
|
||||
},
|
||||
"K5": {
|
||||
"서식": 6,
|
||||
"값": "P(F<기준) %"
|
||||
},
|
||||
"A6": {
|
||||
"서식": 7
|
||||
},
|
||||
"B6": {
|
||||
"서식": 7
|
||||
},
|
||||
"C6": {
|
||||
"서식": 5
|
||||
},
|
||||
"D6": {
|
||||
"서식": 9
|
||||
},
|
||||
"E6": {
|
||||
"서식": 8
|
||||
},
|
||||
"F6": {
|
||||
"서식": 8
|
||||
},
|
||||
"G6": {
|
||||
"서식": 8
|
||||
},
|
||||
"H6": {
|
||||
"서식": 8
|
||||
},
|
||||
"I6": {
|
||||
"서식": 8
|
||||
},
|
||||
"J6": {
|
||||
"서식": 8
|
||||
},
|
||||
"K6": {
|
||||
"서식": 8
|
||||
},
|
||||
"A8": {
|
||||
"서식": 3,
|
||||
"값": "2. 확률 변수 · 민감도 — 토네이도 순(나머지 평균 · 임계면 고정 · 범위 끝 Fs)"
|
||||
},
|
||||
"B8": {
|
||||
"서식": 3
|
||||
},
|
||||
"C8": {
|
||||
"서식": 3
|
||||
},
|
||||
"D8": {
|
||||
"서식": 3
|
||||
},
|
||||
"E8": {
|
||||
"서식": 3
|
||||
},
|
||||
"F8": {
|
||||
"서식": 3
|
||||
},
|
||||
"G8": {
|
||||
"서식": 3
|
||||
},
|
||||
"H8": {
|
||||
"서식": 3
|
||||
},
|
||||
"I8": {
|
||||
"서식": 3
|
||||
},
|
||||
"J8": {
|
||||
"서식": 3
|
||||
},
|
||||
"K8": {
|
||||
"서식": 3
|
||||
},
|
||||
"A9": {
|
||||
"서식": 6,
|
||||
"값": "측점 · 비탈 · 조건"
|
||||
},
|
||||
"B9": {
|
||||
"서식": 6,
|
||||
"값": "변수"
|
||||
},
|
||||
"C9": {
|
||||
"서식": 6,
|
||||
"값": "분포"
|
||||
},
|
||||
"D9": {
|
||||
"서식": 6,
|
||||
"값": "평균"
|
||||
},
|
||||
"E9": {
|
||||
"서식": 6,
|
||||
"값": "표준편차"
|
||||
},
|
||||
"F9": {
|
||||
"서식": 6,
|
||||
"값": "절단 ±σ"
|
||||
},
|
||||
"G9": {
|
||||
"서식": 6,
|
||||
"값": "아래 끝"
|
||||
},
|
||||
"H9": {
|
||||
"서식": 6,
|
||||
"값": "Fs(아래)"
|
||||
},
|
||||
"I9": {
|
||||
"서식": 6,
|
||||
"값": "위 끝"
|
||||
},
|
||||
"J9": {
|
||||
"서식": 6,
|
||||
"값": "Fs(위)"
|
||||
},
|
||||
"K9": {
|
||||
"서식": 6,
|
||||
"값": "순위"
|
||||
},
|
||||
"A10": {
|
||||
"서식": 7
|
||||
},
|
||||
"B10": {
|
||||
"서식": 5
|
||||
},
|
||||
"C10": {
|
||||
"서식": 7
|
||||
},
|
||||
"D10": {
|
||||
"서식": 8
|
||||
},
|
||||
"E10": {
|
||||
"서식": 8
|
||||
},
|
||||
"F10": {
|
||||
"서식": 16
|
||||
},
|
||||
"G10": {
|
||||
"서식": 8
|
||||
},
|
||||
"H10": {
|
||||
"서식": 8
|
||||
},
|
||||
"I10": {
|
||||
"서식": 8
|
||||
},
|
||||
"J10": {
|
||||
"서식": 8
|
||||
},
|
||||
"K10": {
|
||||
"서식": 9
|
||||
},
|
||||
"A12": {
|
||||
"서식": 5,
|
||||
"값": "확률 해석 없음 — E03 [확률 · 민감도] 뒤 [보고서 갱신]"
|
||||
},
|
||||
"B12": {
|
||||
"서식": 5
|
||||
},
|
||||
"C12": {
|
||||
"서식": 5
|
||||
},
|
||||
"D12": {
|
||||
"서식": 5
|
||||
},
|
||||
"E12": {
|
||||
"서식": 5
|
||||
},
|
||||
"F12": {
|
||||
"서식": 5
|
||||
},
|
||||
"G12": {
|
||||
"서식": 5
|
||||
},
|
||||
"H12": {
|
||||
"서식": 5
|
||||
},
|
||||
"I12": {
|
||||
"서식": 5
|
||||
},
|
||||
"J12": {
|
||||
"서식": 5
|
||||
},
|
||||
"K12": {
|
||||
"서식": 5
|
||||
}
|
||||
},
|
||||
"병합": [
|
||||
"A1:K1",
|
||||
"A2:K2",
|
||||
"A3:K3",
|
||||
"A4:K4",
|
||||
"A8:K8",
|
||||
"A12:K12"
|
||||
],
|
||||
"열": {
|
||||
"A": {
|
||||
"폭": 13
|
||||
},
|
||||
"B": {
|
||||
"폭": 16
|
||||
},
|
||||
"C": {
|
||||
"폭": 18
|
||||
},
|
||||
"D": {
|
||||
"폭": 7
|
||||
},
|
||||
"E": {
|
||||
"폭": 8
|
||||
},
|
||||
"F": {
|
||||
"폭": 8
|
||||
},
|
||||
"G": {
|
||||
"폭": 8
|
||||
},
|
||||
"H": {
|
||||
"폭": 8
|
||||
},
|
||||
"I": {
|
||||
"폭": 8
|
||||
},
|
||||
"J": {
|
||||
"폭": 8
|
||||
},
|
||||
"K": {
|
||||
"폭": 8
|
||||
}
|
||||
},
|
||||
"행": {
|
||||
"1": {
|
||||
"높이": 36
|
||||
},
|
||||
"2": {
|
||||
"높이": 30
|
||||
},
|
||||
"3": {
|
||||
"높이": 22
|
||||
},
|
||||
"4": {
|
||||
"높이": 22
|
||||
},
|
||||
"5": {
|
||||
"높이": 30
|
||||
},
|
||||
"8": {
|
||||
"높이": 22
|
||||
},
|
||||
"9": {
|
||||
"높이": 30
|
||||
}
|
||||
},
|
||||
"보기": {
|
||||
"눈금선": false
|
||||
},
|
||||
"인쇄": {
|
||||
"용지": "A4",
|
||||
"방향": "landscape",
|
||||
"맞춤": {
|
||||
"폭": 1,
|
||||
"높이": 0
|
||||
},
|
||||
"여백": {
|
||||
"위": 15,
|
||||
"아래": 15,
|
||||
"왼": 10,
|
||||
"오른": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "s10",
|
||||
"이름": "부록_절편",
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
## 공통
|
||||
|
||||
- 몫: 사용자 몫(표지 · 개요) = 처음 만들 때만 채움 · 다시 만들 때(보고서 갱신) 안 건드림. 프로그램 몫(목차 · 입력조건 · 측점표 · 대표단면 · 3D요약 · 광역요약 · 보강검토 · 암반검토 · 부록_절편) = 갱신 때 통째 다시 씀 · 작업본에 없는 프로그램 몫 시트는 양식 차례 자리에 끼움 · 새 서식은 작업본 서식 표 끝에 붙여 번호를 바꿈.
|
||||
- 시트 차례: 표지 · 목차 · 개요 · 입력조건 · 측점표 · 대표단면 · 3D요약 · 광역요약 · (보강검토) · (암반검토) · 부록_절편.
|
||||
- 몫: 사용자 몫(표지 · 개요) = 처음 만들 때만 채움 · 다시 만들 때(보고서 갱신) 안 건드림. 프로그램 몫(목차 · 입력조건 · 측점표 · 대표단면 · 3D요약 · 광역요약 · 보강검토 · 암반검토 · 확률해석 · 부록_절편) = 갱신 때 통째 다시 씀 · 작업본에 없는 프로그램 몫 시트는 양식 차례 자리에 끼움 · 새 서식은 작업본 서식 표 끝에 붙여 번호를 바꿈.
|
||||
- 시트 차례: 표지 · 목차 · 개요 · 입력조건 · 측점표 · 대표단면 · 3D요약 · 광역요약 · (보강검토) · (암반검토) · (확률해석) · 부록_절편.
|
||||
- PDF: 표지 = 쪽 번호 · 머리말 없음 · 본문 = 머리말(왼 사업명 = 표지 `C8` · 오른 보고서 이름) · 꼬리말 「- n / N -」(목차 = 1 쪽).
|
||||
- 사용자 확정 전 표기 한 규칙 — 기본값 그대로 = 「(기본값 · 사용자 확정 전)」 · 바꿈 = 「(사용자 입력)」 · 물성 = 「문헌 추정치 · 사용자 확정 전」.
|
||||
- 서식 번호(`서식` 표): 1 title · 2 sub · 3 section · 4 label · 5 text · 6 head · 7 body(가운데 글) · 8 fs(0.00) · 9 int(0) · 10 ng(N.G 빨강 채움) · 11 ok(O.K 초록) · 12 notice · 13 num1(#,##0.0) · 14 num0(#,##0) · 15 para(위 맞춤 긴 글) · 16 prop(0.0) · 17 chain(#,##0.00) · 18 head8 · 19 num8(0.00) · 20 num8(0.0) · 21 int8 · 22 목차 내용 · 23 목차 쪽 · 24 목차 장 · 25 sum8(0.00 · 회색) · 26 표지 제목.
|
||||
@@ -126,6 +126,16 @@
|
||||
| 11 · 12 행 꼴(제목 3 · 머리 6) 다음 절리군 하나 = 한 줄(13 행 서식) | A 이름(7) · B 경사방향 · C 경사 · D φj · E cj(16) · F 지속성(13) · G · H 구간 시작 · 끝(7) · 모르면 「-」 · 없으면 「절리군 없음」 |
|
||||
| 절리 줄 다음 한 줄 띄고 15 · 16 행 꼴 · 그 아래 측점 비탈 × 위험 모드 × 조건 = 한 줄(17 행 서식) | A 측점(7) · B 비탈 글(쪽 · 경사 / 방향 · 높이)(5) · C 파괴 형태(평면 · 쐐기 · 전도 + 절리)(7) · D 조건(7) · E Fs · F 기준(8) · G 판정(7 → 10 · 11 · 전도 「검토」) · H 비고(블록 안 생김 · 미끄러짐 면 · 지속성)(5) · 위험 모드 없는 비탈 = 「없음」 한 줄 |
|
||||
|
||||
## s12 확률해석 (프로그램 몫 · E03 확률 해석 저장본 · 없으면 시트를 뺌 · 가로 쪽)
|
||||
|
||||
| 칸 | 내용 |
|
||||
| --- | --- |
|
||||
| `A3` (A:K 병합 · 서식 12) | 「변동계수 · 분포 · 표본 = 문헌 기본값 후보(근거 E03 탭)」 + 사용자 확정 전 표기(모든 기록이 `prob_defaults.json` 그대로면 기본값 · 아니면 사용자 입력) |
|
||||
| 4 · 5 행 꼴(제목 3 · 머리 6) 다음 (측점 · 비탈 · 조건)마다 한 줄(6 행 서식) | A 측점(7) · B 비탈 · 조건(7) · C 방법 · 임계면 고정 / 매회 탐색(5) · D 유효 표본(9) · E 결정론 Fs · F 평균 · G 표준편차 · H Pf % · I β 정규 · J β 대수정규 · K P(F < 기준) %(8) |
|
||||
| 한 줄 띄고 8 · 9 행 꼴 · 그 아래 기록마다 변수 = 한 줄(10 행 서식 · 토네이도 순) | A 측점 · 비탈 · 조건(7) · B 변수(5) · C 분포(7) · D 평균 · E 표준편차(8) · F 절단 ±σ(16 · 없으면 「-」) · G 아래 끝 · H 그때 Fs · I 위 끝 · J 그때 Fs(8) · K 순위(9) |
|
||||
|
||||
- 저장본 = (측점 · 비탈 · 조건)마다 가장 최근 하나 · 지금 2D 결과 측점 지문에 붙은 것만.
|
||||
|
||||
## s10 부록_절편 (프로그램 몫 · 대표 단면마다 한 쪽)
|
||||
|
||||
양식 1~5 행 = 덩이 꼴(1 제목 · 2 단위 · 3 머리 · 4 절편 줄 서식 · 5 합계 서식). 대표 단면마다 지배 조건(Fs / 기준 가장 낮음) 임계면의 절편표.
|
||||
|
||||
@@ -38,7 +38,7 @@ PRINT_KEYS = {"용지", "방향", "배율", "맞춤", "여백", "머리행", "
|
||||
A1 = re.compile(r"^([A-Z]{1,3})(\d+)$")
|
||||
NAMES = [
|
||||
"표지", "목차", "개요", "입력조건", "측점표", "대표단면", "3D요약", "광역요약", "보강검토", "암반검토",
|
||||
"부록_절편",
|
||||
"확률해석", "부록_절편",
|
||||
] # fmt: skip
|
||||
|
||||
needs_node = pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
||||
@@ -151,7 +151,7 @@ def test_양식_계약():
|
||||
book = _form()
|
||||
_contract(book)
|
||||
assert [s["이름"] for s in book["시트"]] == NAMES
|
||||
ids = ["s1", "s9", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s11", "s10"]
|
||||
ids = ["s1", "s9", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s11", "s12", "s10"]
|
||||
assert [s["id"] for s in book["시트"]] == ids
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@ def test_양식_인쇄_쪽나눔_머리행_그림():
|
||||
"광역요약": 0,
|
||||
"보강검토": 0,
|
||||
"암반검토": 0,
|
||||
"확률해석": 0,
|
||||
}
|
||||
# N.G 빨강 조건부 서식 — 측점표 판정 열 · 서식 표에 N.G 칸 서식(빌더가 인쇄 · xlsx 용으로 직접 씀)
|
||||
assert by["측점표"]["조건부서식"][0]["범위"][0]["c0"] == 9
|
||||
@@ -239,9 +240,9 @@ def test_견본_pdf_쪽수_머리행(png):
|
||||
sections = [i for i, t in enumerate(texts) if "대표 단면" in t and "안전율 Fs" in t]
|
||||
assert len(sections) == 2 # 단면마다 한 쪽
|
||||
# 표지 1 + 목차 1 + 개요 1 + 입력조건 1 + 측점표 n + 대표단면 2 + 3D 1 + 광역 1 + 보강 1 + 암반 1
|
||||
# + 부록 1
|
||||
# + 확률 1 + 부록 1
|
||||
# (양식 빈 판)
|
||||
assert len(pages) == 1 + 1 + 1 + 1 + len(stab) + 2 + 1 + 1 + 1 + 1 + 1
|
||||
assert len(pages) == 1 + 1 + 1 + 1 + len(stab) + 2 + 1 + 1 + 1 + 1 + 1 + 1
|
||||
assert all(float(pages[i].mediabox.width) > float(pages[i].mediabox.height) for i in stab)
|
||||
html = spreadsheet.print_htmls([{"book": book, "figures": {k: png for k in figs}}])[0]
|
||||
assert html.count("<img ") == 3 # 단면 2 + 3D 띠 1
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
- 임계면 고정: 평균 줄 F = 저장된 결정론 F · Pf · β · 표본 수 · 같은 요청 = 저장본(다시 안 셈) · 기록 목록
|
||||
- 대표 단면 아님 · 단면에 없는 물성 · 수위선 아닌데 지하수위 변수 · 변수 없음 = 422
|
||||
- 매회 탐색 일꾼(프로세스 풀 · 진행 줄 · 결과 파일)
|
||||
- E05 확률해석 시트(있을 때만 · 결과 = 저장본 · 기본값 그대로 = 사용자 확정 전 · 바꾸면 사용자 입력 표기)
|
||||
- 화면 계산(logic/E03_StabSection_Prob.ts): 기본값 후보 · 물성 덮어쓰기 · 요청 꼴 = 서버 시험 요청
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_537_1_e03_prob_route.py -q
|
||||
"""
|
||||
@@ -14,6 +16,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
@@ -196,5 +199,97 @@ def test_search_worker_process():
|
||||
assert all(abs(f - 1.139) < 0.02 for f in out["F"])
|
||||
|
||||
|
||||
def test_e05_prob_sheet(project, monkeypatch):
|
||||
"""E05 s12 — 측점 줄 = 저장본 값 · 변수 줄 = 토네이도 순 · 기본값 그대로 표기 · 없으면 시트 뺌."""
|
||||
from E05_StabReport import E05_StabReport_Build as build
|
||||
|
||||
recs = rp.records(project)
|
||||
assert len(recs) == 1
|
||||
rec = recs[0]
|
||||
book = build.form()
|
||||
build.build_program(book, build.gather(project))
|
||||
s12 = next(s for s in book["시트"] if s["id"] == "s12")
|
||||
cells = s12["칸"]
|
||||
st = rec["result"]["stats"]
|
||||
assert cells["E6"]["값"] == pytest.approx(rec["fs_deterministic"], abs=1e-6)
|
||||
assert cells["F6"]["값"] == pytest.approx(st["mean"], abs=1e-6)
|
||||
assert cells["H6"]["값"] == pytest.approx(100 * st["pf"], abs=1e-6)
|
||||
assert cells["J6"]["값"] == pytest.approx(st["beta_lognormal"], abs=1e-6)
|
||||
assert "사용자 입력" in cells["A3"]["값"] # 표본 400 ≠ 기본 1000
|
||||
d = rp.load_defaults()
|
||||
plain = {
|
||||
**rec["request"],
|
||||
**{k: d["run"][k] for k in ("n", "sampling", "seed", "mode", "method")},
|
||||
}
|
||||
assert build.prob._is_default({"request": plain}, d)
|
||||
plain["variables"] = [{**plain["variables"][0], "cov": 0.25}]
|
||||
assert not build.prob._is_default({"request": plain}, d)
|
||||
first = rec["result"]["sensitivity"]["tornado"][0]["name"]
|
||||
assert cells["B10"]["값"] == first and cells["K10"]["값"] == 1
|
||||
monkeypatch.setattr(build.prob, "read", lambda root: [])
|
||||
empty = build.form()
|
||||
build.build_program(empty, build.gather(project))
|
||||
assert "s12" not in {s["id"] for s in empty["시트"]}
|
||||
|
||||
|
||||
TYPESCRIPT = ROOT / "config" / "node_modules" / "typescript"
|
||||
LOGIC_TS = ROOT / "E03_StabSection" / "logic" / "E03_StabSection_Prob.ts"
|
||||
LOGIC_SCRIPT = r"""
|
||||
const ts = require(process.argv[1]);
|
||||
const fs = require("fs");
|
||||
const src = fs.readFileSync(process.argv[2], "utf8");
|
||||
const opts = { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 };
|
||||
const js = ts.transpileModule(src, { compilerOptions: opts }).outputText;
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", js)(box.exports, box);
|
||||
const m = box.exports;
|
||||
const setup = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
const cache = m.fillCache(setup, null);
|
||||
const req = m.probRequest(setup, cache);
|
||||
cache.vars[m.varKey("colluvium", "c")].value = 0.5;
|
||||
process.stdout.write(JSON.stringify({ req, changed: m.changedCount(setup, cache),
|
||||
ticks: m.axisTicks(0.83, 1.47, 5) }));
|
||||
"""
|
||||
|
||||
|
||||
@pytest.mark.skipif(not TYPESCRIPT.is_dir(), reason="config/node_modules 없음")
|
||||
def test_logic_ts_request_matches_server():
|
||||
"""기본값 후보(붕적토 c 0.4 덮어씀) → 요청 꼴 = 서버 ProbRequest 로 읽힘 · 바꾼 줄 수 · 눈금."""
|
||||
setup = {
|
||||
"side": "left",
|
||||
"condition": "wet",
|
||||
"materials": [
|
||||
{"id": "weathered_soil", "name": "풍화토", "c": 15, "phi": 30, "gamma_t": 18},
|
||||
{"id": "colluvium", "name": "붕적토", "c": 5, "phi": 27, "gamma_t": 17.5},
|
||||
],
|
||||
"groundwater": {"mode": "line", "ru": None},
|
||||
"defaults": rp.load_defaults(),
|
||||
}
|
||||
out = subprocess.run(
|
||||
["node", "-e", LOGIC_SCRIPT, str(TYPESCRIPT), str(LOGIC_TS)],
|
||||
input=json.dumps(setup),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
check=True,
|
||||
)
|
||||
got = json.loads(out.stdout)
|
||||
req = rp.ProbRequest(**got["req"])
|
||||
assert (req.side, req.condition, req.n, req.sampling, req.mode) == (
|
||||
"left",
|
||||
"wet",
|
||||
1000,
|
||||
"lhs",
|
||||
"fixed",
|
||||
)
|
||||
by = {(v.material, v.prop): v for v in req.variables}
|
||||
assert len(by) == 6 # 물 σ 0 = 뺌
|
||||
assert by[("colluvium", "c")].cov == 0.4 and by[("weathered_soil", "c")].cov == 0.3
|
||||
assert by[("weathered_soil", "c")].dist == "lognormal" and by[("colluvium", "phi")].cov == 0.1
|
||||
assert all(v.trunc_sigma == 3.0 for v in req.variables)
|
||||
assert got["changed"] == 1
|
||||
assert got["ticks"][0] <= 0.83 and got["ticks"][-1] >= 1.47 and 0 not in got["ticks"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-q"]))
|
||||
|
||||
Reference in New Issue
Block a user