"""Optuna 후처리 진행 한 줄 로그 — 웹 프로그레스바 파서용. 실매 엔진과 무관. 형식: OPTUNA_POST_PROGRESS pct=.. step=.. total=.. stage=.. axis=a/b 설명 """ from __future__ import annotations import threading from typing import Any, Optional _tls = threading.local() def _ctx() -> dict: c = getattr(_tls, "ctx", None) if not isinstance(c, dict): c = { "log": None, "step": 1, "total": 1, "stage": "idle", "n_axes": 1, "axis_i": 0, "axis_trials": 0, } _tls.ctx = c return c def begin_job(log: Any, total: int) -> None: c = _ctx() c["log"] = log c["total"] = max(1, int(total or 1)) c["step"] = 0 c["stage"] = "start" c["n_axes"] = 1 c["axis_i"] = 0 c["axis_trials"] = 0 emit(stage="start", detail="후처리 시작") def begin_unit(step: int, stage: str, detail: str = "") -> None: c = _ctx() c["step"] = max(1, int(step)) c["stage"] = str(stage or "") c["n_axes"] = 1 c["axis_i"] = 0 c["axis_trials"] = 0 emit(stage=stage, detail=detail or stage) def next_unit(stage: str, detail: str = "") -> int: c = _ctx() step = int(c.get("step") or 0) + 1 begin_unit(step, stage, detail) return step def set_ob_axes(n_axes: int, axis_trials: int) -> None: c = _ctx() c["n_axes"] = max(1, int(n_axes or 1)) c["axis_trials"] = max(0, int(axis_trials or 0)) def begin_ob_axis(axis_name: str, axis_i: int, axis_trials: Optional[int] = None) -> None: c = _ctx() c["axis_i"] = max(0, int(axis_i)) if axis_trials is not None: c["axis_trials"] = max(0, int(axis_trials)) emit( stage=str(c.get("stage") or "ob"), detail=str(axis_name or ""), axis_done=0, axis_total=int(c.get("axis_trials") or 0), ) def on_ob_axis_trial(done: int, total: int, axis_name: str) -> None: emit( stage=str(_ctx().get("stage") or "ob"), detail=str(axis_name or ""), axis_done=int(done), axis_total=int(total), ) def finish_job(log: Optional[Any] = None) -> None: c = _ctx() if log is not None: c["log"] = log c["step"] = int(c.get("total") or 1) emit(stage="done", detail="", done=True) def emit( *, stage: str, detail: str = "", axis_done: int = 0, axis_total: int = 0, done: bool = False, ) -> None: c = _ctx() log = c.get("log") if log is None: return step = max(0, int(c.get("step") or 0)) total = max(1, int(c.get("total") or 1)) n_axes = max(1, int(c.get("n_axes") or 1)) axis_i = max(0, int(c.get("axis_i") or 0)) ax_tot = int(axis_total or c.get("axis_trials") or 0) ax_done = int(axis_done or 0) if done: pct = 100.0 else: inner = 0.0 if ax_tot > 0: inner = (axis_i + min(1.0, ax_done / float(ax_tot))) / float(n_axes) elif n_axes > 1: inner = axis_i / float(n_axes) frac = (max(0, step - 1) + min(1.0, inner)) / float(total) pct = round(min(99.9, max(0.0, 100.0 * frac)), 1) bits = [ f"OPTUNA_POST_PROGRESS pct={pct}", f"step={max(1, step) if step else 0}", f"total={total}", f"stage={stage}", f"axis={ax_done}/{ax_tot}", ] if detail: bits.append(str(detail).replace("\n", " ")[:80]) if done: bits.append("상세가능") try: log.info("%s", " ".join(bits)) except Exception: pass