feat(E03): 판정 면 칸(원호 · 비원호 · 면 확인 · 모형 끝 면) + 탐색 한계 고르기 (PLAN 567-1)

- 목록 판정 면 칸 · 결과 방법 줄 · 면 확인 · 모형 끝 면 배지
- 해석 설정 탐색 한계(모형 끝 / 비탈 끝 + 2H / 비탈 끝 + 정한 m) — [해석] 이 저장 + 그 값으로 셈
- Router 설정 저장본 · 지문 · 일꾼 kwargs · 단면 · 가정 · 확률 길

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 20:38:55 +09:00
co-authored by Claude Sonnet 5.5
parent 97bdc0a92d
commit 79d33601f2
10 changed files with 279 additions and 13 deletions
+42 -2
View File
@@ -55,6 +55,7 @@ from E03_StabSection import E03_StabSection_View as view
from E03_StabSection import E03_StabSection_Walls as walls_mod
from E03_StabSection import E03_StabSection_Worker as worker
from E03_StabSection.E03_StabSection_Engine_Model import (
SEARCH_REACH,
ground_from_cross,
section_model_from_b06,
widen_ground,
@@ -67,6 +68,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
KIND_STATION = worker.KIND_STATION
KIND_SUMMARY = "stab2d_summary"
REPRESENTATIVE = Path("E03_StabSection") / "representative.json"
SETTINGS = Path("E03_StabSection") / "settings.json"
ENGINE_FILES = (
"E03_StabSection_Types.py",
"E03_StabSection_Engine_Model.py",
@@ -91,6 +93,31 @@ class RepresentativeRequest(BaseModel):
overrides: dict[str, bool | None]
class AnalyzeRequest(BaseModel):
#: 2D 탐색 한계 — "model_end" · "2h" · 수(비탈 끝 + m) · None = 지금 저장값 그대로
search_reach: str | float | None = None
def _clean_reach(value: Any) -> str | float:
"""탐색 한계 값 검사 — 모르는 값은 기본."""
if value in ("model_end", "2h"):
return value
try:
reach = float(value)
except (TypeError, ValueError):
return SEARCH_REACH
return reach if math.isfinite(reach) and reach > 0 else SEARCH_REACH
def search_reach_of(root: Path) -> str | float:
"""작업본 해석 설정의 탐색 한계 — 없으면 서버 기본(`SEARCH_REACH`)."""
try:
data = json.loads((root / SETTINGS).read_text(encoding="utf-8"))
except (OSError, ValueError):
return SEARCH_REACH
return _clean_reach(data.get("search_reach"))
@lru_cache(maxsize=1)
def engine_hash() -> str:
"""엔진 코드 지문 — 코드가 바뀌면 모든 측점을 다시 셈."""
@@ -140,6 +167,9 @@ def plan_run(root: Path, ground: dict[str, Any]) -> dict[str, Any]:
"road_surcharge_kpa": float(ground.get("road_surcharge_kpa") or 0.0),
"groundwater": ground.get("groundwater"),
}
reach = search_reach_of(root)
if reach != SEARCH_REACH:
shared["search_reach"] = reach # 기본값이면 빼서 옛 지문 그대로
resolved = {chainage_key(r["chainage_m"]): r for r in ground_model.resolve_stations(ground)}
try:
found = _walls(str(source), copy_fp)
@@ -255,6 +285,7 @@ def run_all(root: Path, plan: dict[str, Any], report: Any) -> int:
"conditions": list(shared["conditions"]),
"seismic_kh": shared["seismic_kh"],
"road_surcharge_kpa": shared["road_surcharge_kpa"],
"search_reach": shared.get("search_reach", SEARCH_REACH),
},
"items": job_items,
}
@@ -314,9 +345,15 @@ def _overrides(root: Path) -> dict[str, bool]:
@router.post("/{project_id}/stab/analyze2d", response_model=None)
async def analyze2d(project_id: UUID) -> dict[str, Any]:
"""전 측점 해석(뒤에서) — 지문이 같으면 다시 안 셈(`fresh`) · 진행은 `GET …/stab/progress`."""
async def analyze2d(project_id: UUID, req: AnalyzeRequest | None = None) -> dict[str, Any]:
"""전 측점 해석(뒤에서) — 지문이 같으면 다시 안 셈(`fresh`) · 진행은 `GET …/stab/progress`.
본문 `search_reach` 가 있으면 해석 설정에 저장한 뒤 그 값으로 셈."""
root, _ = await e02._project(project_id)
if req is not None and req.search_reach is not None:
reach = _clean_reach(req.search_reach)
if reach != search_reach_of(root):
atomic_write_json(root / SETTINGS, {"search_reach": reach})
plan = plan_run(root, _ground(root))
if read_derived(root, KIND_SUMMARY, plan["fp"]):
return {"status": "fresh", "fingerprint": plan["fp"]}
@@ -375,6 +412,7 @@ async def result2d(
"running": running,
"conditions": plan["shared"]["conditions"],
"factors": plan["factors"],
"search_reach": plan["shared"].get("search_reach", SEARCH_REACH),
"stations": rows,
"missing_stations": missing,
}
@@ -387,6 +425,7 @@ async def result2d(
"editable": editable,
"running": running,
**{k: v for k, v in summary.items() if k != "stations"},
"search_reach": plan["shared"].get("search_reach", SEARCH_REACH),
"stations": rows,
"missing_stations": missing,
}
@@ -413,6 +452,7 @@ async def section(project_id: UUID, station_key: str) -> dict[str, Any]:
"ground": [tuple(p) for p in value["inputs"]["ground"]],
"road_surcharge_kpa": plan["shared"]["road_surcharge_kpa"],
"walls": walls_mod.walls_of(entry),
"search_reach": plan["shared"].get("search_reach", SEARCH_REACH),
}
dry = section_model_from_b06(cross, design, **kw)
water, rules = {}, {}