diff --git a/E03_StabSection/E03_StabSection_UI_Section.ts b/E03_StabSection/E03_StabSection_UI_Section.ts index 7a50b1f15..8bbd73618 100644 --- a/E03_StabSection/E03_StabSection_UI_Section.ts +++ b/E03_StabSection/E03_StabSection_UI_Section.ts @@ -305,7 +305,14 @@ export function createSectionCanvas(onPick: (index: number | null) => void): Sec labels.append( svgEl( "text", - { x: ax + 8, y: ay - 10, class: "e03-label e03-label--fs", "data-role": "fs-label" }, + { + x: ax, + y: ay, + dx: "0.6em", + dy: "-0.7em", + class: "e03-label e03-label--fs", + "data-role": "fs-label", + }, `Fs = ${fmt(critical.fs)}`, ), ); @@ -313,7 +320,7 @@ export function createSectionCanvas(onPick: (index: number | null) => void): Sec labels.append( svgEl( "text", - { x: ax + 8, y: ay + 6, class: "e03-label" }, + { x: ax, y: ay, dx: "0.7em", dy: "0.5em", class: "e03-label" }, `Xc ${fmt(critical.center[0], 2)} · Zc ${fmt(critical.center[1], 2)} · R ${fmt(critical.radius, 2)} m`, ), ); @@ -351,7 +358,10 @@ export function createSectionCanvas(onPick: (index: number | null) => void): Sec maxScale: MAX_SCALE, home: focusHome(fit, view, analysis, width, height, MAX_SCALE), initial: zoom, - onChange: (state) => (zoom = state), + onChange: (state) => { + zoom = state; + labels.style.setProperty("--e03-label-k", String(1 / state.scale)); // 글자는 확대 안 함 + }, }); }; diff --git a/E03_StabSection/E03_StabSection_UI_Style.css b/E03_StabSection/E03_StabSection_UI_Style.css index 63c515748..c7bbb4419 100644 --- a/E03_StabSection/E03_StabSection_UI_Style.css +++ b/E03_StabSection/E03_StabSection_UI_Style.css @@ -420,14 +420,14 @@ .e03-label { fill: var(--color-text); - font-size: 12px; + font-size: calc(12px * var(--e03-label-k, 1)); paint-order: stroke; stroke: var(--e03-plot); - stroke-width: 3px; + stroke-width: calc(3px * var(--e03-label-k, 1)); } .e03-label--fs { - font-size: 14px; + font-size: calc(14px * var(--e03-label-k, 1)); font-weight: 700; } diff --git a/E05_StabReport/E05_StabReport_Build.py b/E05_StabReport/E05_StabReport_Build.py index 0aea12383..d0be14542 100644 --- a/E05_StabReport/E05_StabReport_Build.py +++ b/E05_StabReport/E05_StabReport_Build.py @@ -137,6 +137,7 @@ def _row2d(row: dict[str, Any], method: str) -> dict[str, Any]: cond: sides[side]["fs"] for cond, sides in cases.items() if side in sides }, "fs_min": min(values) if values else None, + "min_by_condition": row.get("min") or {}, "method": method, "verdict": "ng" if "ng" in verdicts else "ok" if verdicts == {"ok"} else None, "representative": row.get("representative") or {}, @@ -556,19 +557,23 @@ def fill_3d(sheet: dict[str, Any], inputs: dict[str, Any]) -> tuple[str, str]: ground = inputs["ground"] labels = _labels(inputs["stations"]) rows2d = inputs["result2d"]["stations"] - crit = _criterion(ground, "dry") sections = [] for i, sec in enumerate(inputs["sections3d"]): r = 20 + i _copy_row(sheet, 20, r, "ABCDEGH") start, end = float(sec["start_m"]), float(sec["end_m"]) + cond = sec.get("condition") or "dry" # 2D 는 3D 와 같은 조건끼리 견줌 inside = [ - float(s["fs_min"]) + float(s["min_by_condition"][cond]) for s in rows2d - if s.get("fs_min") is not None and start - 1e-6 <= float(s["chainage_m"]) <= end + 1e-6 + if s["min_by_condition"].get(cond) is not None + and start - 1e-6 <= float(s["chainage_m"]) <= end + 1e-6 ] f3 = float(sec["f_min"]) - ok = f3 >= crit - 1e-9 + if sec.get("verdict") in ("ok", "ng"): # 판정 = E04 엔진 값(기준안전율도 E04) + ok = sec["verdict"] == "ok" + else: + ok = f3 >= _criterion(ground, cond) - 1e-9 _set(sheet, f"A{r}", f"{i + 1}구간" + (" (미리보기)" if sec.get("preview") else "")) _set(sheet, f"B{r}", _label_at(labels, start)) _set(sheet, f"C{r}", _label_at(labels, end)) diff --git a/resources/tester/test_528_project_groups.py b/resources/tester/test_528_project_groups.py index 6a912e3f4..aedfd6fe7 100644 --- a/resources/tester/test_528_project_groups.py +++ b/resources/tester/test_528_project_groups.py @@ -165,7 +165,7 @@ def test_e01_requires_b06(monkeypatch): call() assert e.value.status_code == 404 seen, call = _e01(monkeypatch, [{**src, "project_year": 2026, "section_complete": 1}]) - assert call() == {"project_id": "stab-id", "source_fingerprint": "f" * 40} + assert call() == {"project_id": "stab-id", "source_fingerprint": "f" * 40, "warnings": []} assert seen["copied"] == ("stab-id", "s" * 36) assert seen["kind"] == "STABILITY" and seen["source_project_id"] == "s" * 36 assert seen["name"] == "임도 안정성평가" diff --git a/resources/tester/test_534_3_e05_report.py b/resources/tester/test_534_3_e05_report.py index 19896df36..f88f946c9 100644 --- a/resources/tester/test_534_3_e05_report.py +++ b/resources/tester/test_534_3_e05_report.py @@ -157,7 +157,15 @@ def project(tmp_path, monkeypatch) -> Path: { "fingerprint": "3d-1", "settings": {}, - "summary": {"start_m": 0.0, "end_m": 200.0, "f_min": 1.42, "volume": 830.0}, + "summary": { + "start_m": 0.0, + "end_m": 200.0, + "f_min": 1.42, + "volume": 830.0, + "condition": "wet", + "reference_fs": 1.3, + "verdict": "ok", + }, } ), encoding="utf-8", @@ -215,8 +223,8 @@ def test_처음_만듦_측점수_NG_그림(project): assert s3["A26"]["값"] == "풍화토" and s3["B32"]["값"].startswith("0 ~ 100 m 위 지층") assert s3["A32"]["값"] == "붕적토" and s3["B13"]["값"] == 1.3 s6 = by["3D요약"]["칸"] - assert s6["D20"]["값"] == 1.42 and s6["H20"] == {"값": "N.G", "서식": build.ST_NG} - assert s6["E20"]["값"] == pytest.approx(1.11) # 0~200 m 안 2D 최소 + assert s6["D20"]["값"] == 1.42 and s6["H20"] == {"값": "O.K", "서식": build.ST_OK} # E04 판정 + assert s6["E20"]["값"] == pytest.approx(1.11) # 0~200 m 안 2D 우기 최소(3D 와 같은 조건) assert (build.initial_dir(project) / build.REPORT).is_file() assert saved["fingerprint"] == build.analysis_fingerprint(project)