feat(564-1 · 564-3): 체인 단계 상태 = mark_stab_step(565-1) · E02 떠날 때 바뀐 값 저장(beforeLeave 560-4)
- 체인 단계마다 run → done · skip · error · cancel 을 대시보드 단계 행에 적음 · 지반 = 시작 때 done · 적기 실패는 체인을 막지 않음 - E02 beforeLeave — 고칠 수 있고 바뀐 값이 있으면 PUT /stab/ground 뒤 이동 · 틀린 칸 · 실패 = 머묾 - 시험 test_564_1 — 단계 상태 적기 차례 · 적기 실패 격리 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,8 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from common_util.common_util_workflow_state import mark_stab_step
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
#: 단계 — (열쇠, 이름, 비율 몫) · 몫 = 1.2 km 실측 시간 비(2D 1~3 분 · 3D 건기 · 우기 약 25 분)
|
||||
@@ -54,8 +56,14 @@ def idle() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def _mark_stage(project_id: str, key: str, state: str) -> None:
|
||||
"""대시보드 단계 상태 — ponytail: 지금은 안 적음 · 565-1 함수가 들어오면 여기서 부름."""
|
||||
async def _mark_stage(project_id: str, key: str, state: str, message: str | None = None) -> None:
|
||||
"""대시보드 단계 상태(565-1) — 적기 실패는 체인을 막지 않음."""
|
||||
if not project_id:
|
||||
return
|
||||
try:
|
||||
await mark_stab_step(project_id, key, state, message)
|
||||
except Exception: # noqa: BLE001
|
||||
logger.exception("E02 체인 단계 상태 적기 실패: %s %s", key, state)
|
||||
|
||||
|
||||
async def run_chain(
|
||||
@@ -70,10 +78,11 @@ async def run_chain(
|
||||
if stopped():
|
||||
job.update(cancelled=True, message="끊음")
|
||||
row["state"] = "cancel"
|
||||
await _mark_stage(project_id, key, "cancel")
|
||||
break
|
||||
job.update(stage=key, stage_name=name, message=name)
|
||||
row["state"] = "run"
|
||||
_mark_stage(project_id, key, "run")
|
||||
await _mark_stage(project_id, key, "run")
|
||||
|
||||
def report(frac: float, message: str, base: float = base, weight: float = weight) -> None:
|
||||
frac = min(1.0, max(0.0, float(frac)))
|
||||
@@ -92,7 +101,8 @@ async def run_chain(
|
||||
errors.append(f"{name}: {getattr(error, 'detail', None) or error}")
|
||||
finally:
|
||||
row["seconds"] = round(time.perf_counter() - started, 1)
|
||||
_mark_stage(project_id, key, row["state"])
|
||||
failed = errors[-1] if row["state"] == "error" else None
|
||||
await _mark_stage(project_id, key, row["state"], failed)
|
||||
base += weight
|
||||
job["percent"] = min(99, int(100 * base / total))
|
||||
job["error"] = " · ".join(errors) or None
|
||||
@@ -266,6 +276,7 @@ def start(project_id: UUID, project_root: Path) -> dict[str, Any]:
|
||||
|
||||
async def work() -> None:
|
||||
try:
|
||||
await _mark_stage(pid, "ground", "done") # 지반 = 길에서 저장을 마친 뒤 시작
|
||||
await run_chain(job, steps, lambda: _STOPS.get(pid, False), pid)
|
||||
finally:
|
||||
job["running"] = False
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
* GET /api/projects/{id}/stab/ground → 작업본(없으면 초기값) · 기본값 · 고칠 수 있는지.
|
||||
* 조작 = sessionStorage 캐시만(자동저장 없음) · [해석 시작] = POST …/stab/run(서버가 다시 검사 ·
|
||||
* 저장 → 2D · 3D · 암반 · 광역 · 보고서 체인 · PLAN 564-3) · 진행 = GET …/stab/run/progress(다른
|
||||
* 페이지에 갔다 와도 이어 보임) · [끊기] · [초기화] = POST …/reset(초기값 덮어씀).
|
||||
* 페이지에 갔다 와도 이어 보임) · [끊기] · 떠날 때 바뀐 값 = PUT …/stab/ground(beforeLeave) ·
|
||||
* [초기화] = POST …/reset(초기값 덮어씀).
|
||||
* 검사 = logic/E02_StabGround_Check(파이썬 짝).
|
||||
* 칸 위 마우스 = 틀린 까닭 · 기본값 · 근거(지식DB 07 · KDS 11 70 05).
|
||||
* ========================================================================== */
|
||||
@@ -138,7 +139,11 @@ export function renderE02StabGround(root: HTMLElement): void {
|
||||
runBox,
|
||||
actions,
|
||||
);
|
||||
mountGroupStep(root, ROUTES.E02_STAB_GROUND, { mainContent: main, leftPanel: panel });
|
||||
mountGroupStep(root, ROUTES.E02_STAB_GROUND, {
|
||||
mainContent: main,
|
||||
leftPanel: panel,
|
||||
beforeLeave: async () => !view?.editable || !dirty || store(),
|
||||
});
|
||||
if (!projectId) return void main.append(hook(createText(t("E02_NoProject")), "e02-empty"));
|
||||
|
||||
let view: E02View | null = null;
|
||||
@@ -257,17 +262,42 @@ export function renderE02StabGround(root: HTMLElement): void {
|
||||
render();
|
||||
};
|
||||
|
||||
/** [해석 시작] — 서버가 검사 · 저장(잠금판은 이 순간만) → 체인은 뒤에서 */
|
||||
async function runChain(): Promise<void> {
|
||||
if (!view?.editable || running) return;
|
||||
/** 틀린 칸이 있으면 첫 까닭을 알리고 false */
|
||||
function valid(): boolean {
|
||||
const errors = mark();
|
||||
if (errors.length) {
|
||||
if (view && errors.length) {
|
||||
showToast(
|
||||
`${t("E02_Invalid")} ${errors.length} — ${errorText(errors[0], view.defaults)}`,
|
||||
"error",
|
||||
);
|
||||
return;
|
||||
}
|
||||
return errors.length === 0;
|
||||
}
|
||||
|
||||
/** 떠날 때(560-4 beforeLeave) — 해석 없이 바뀐 값만 저장 · 틀리거나 실패 = 머묾 */
|
||||
async function store(): Promise<boolean> {
|
||||
if (!view || !projectId || !valid()) return false;
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
await request(`/projects/${projectId}/stab/ground`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ ground: view.ground }),
|
||||
});
|
||||
sessionStorage.removeItem(cacheKey(projectId));
|
||||
dirty = false;
|
||||
showToast(t("E02_Saved"), "success");
|
||||
return true;
|
||||
} catch (error) {
|
||||
showToast(error instanceof Error ? error.message : t("Common_Status_Error"), "error");
|
||||
return false;
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
/** [해석 시작] — 서버가 검사 · 저장(잠금판은 이 순간만) → 체인은 뒤에서 */
|
||||
async function runChain(): Promise<void> {
|
||||
if (!view?.editable || running || !valid()) return;
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
const body = await request<GroundResponse & RunProgress>(`/projects/${projectId}/stab/run`, {
|
||||
|
||||
@@ -231,3 +231,25 @@ def test_틀린_지반은_시작_안_함(tmp_path, monkeypatch):
|
||||
with pytest.raises(HTTPException) as bad:
|
||||
asyncio.run(router.run_chain(PID, router.GroundRequest(ground=ground)))
|
||||
assert bad.value.status_code == 422 and not chain._JOBS.get(str(PID))
|
||||
|
||||
|
||||
def test_단계_상태_적기(monkeypatch):
|
||||
marks = []
|
||||
|
||||
async def fake_mark(pid, step, state, message=None):
|
||||
marks.append((step, state, message))
|
||||
if step == "rock":
|
||||
raise RuntimeError("DB 끊김") # 적기 실패는 체인을 막지 않음
|
||||
|
||||
monkeypatch.setattr(chain, "mark_stab_step", fake_mark)
|
||||
job = _Job({**chain.idle(), "running": True})
|
||||
steps = _fake([], states={"report": "skip"}, fail={"area"})
|
||||
asyncio.run(chain.run_chain(job, steps, lambda: False, "pid"))
|
||||
assert [m[:2] for m in marks if m[1] != "run"] == [
|
||||
("2d", "done"),
|
||||
("3d", "done"),
|
||||
("rock", "done"),
|
||||
("area", "error"),
|
||||
("report", "skip"),
|
||||
]
|
||||
assert marks[0] == ("2d", "run", None) and "area 망가짐" in marks[7][2]
|
||||
|
||||
Reference in New Issue
Block a user